diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md
index b3bc9845e..b53a3beb9 100644
--- a/docs/ReleaseNotes.md
+++ b/docs/ReleaseNotes.md
@@ -10,6 +10,7 @@
- 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 `ZDIFF`, `ZDIFFSTORE`, `ZINTER`, `ZINTERCARD`, and `ZUNION` with `.SortedSetCombine()`/`.SortedSetCombineAsync()`, `.SortedSetCombineWithScores()`/`.SortedSetCombineWithScoresAsync()`, and `.SortedSetIntersectionLength()`/`.SortedSetIntersectionLengthAsync()` ([#2075 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2075))
- Adds: Support for `SINTERCARD` with `.SetIntersectionLength()`/`.SetIntersectionLengthAsync()` ([#2078 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2078))
## 2.5.61
diff --git a/src/StackExchange.Redis/Enums/RedisCommand.cs b/src/StackExchange.Redis/Enums/RedisCommand.cs
index b4d3c5fd7..27e1e32ff 100644
--- a/src/StackExchange.Redis/Enums/RedisCommand.cs
+++ b/src/StackExchange.Redis/Enums/RedisCommand.cs
@@ -196,7 +196,11 @@ internal enum RedisCommand
ZADD,
ZCARD,
ZCOUNT,
+ ZDIFF,
+ ZDIFFSTORE,
ZINCRBY,
+ ZINTER,
+ ZINTERCARD,
ZINTERSTORE,
ZLEXCOUNT,
ZPOPMAX,
@@ -216,6 +220,7 @@ internal enum RedisCommand
ZREVRANK,
ZSCAN,
ZSCORE,
+ ZUNION,
ZUNIONSTORE,
UNKNOWN,
diff --git a/src/StackExchange.Redis/Enums/SetOperation.cs b/src/StackExchange.Redis/Enums/SetOperation.cs
index b88b0c8f0..f7cf4f1df 100644
--- a/src/StackExchange.Redis/Enums/SetOperation.cs
+++ b/src/StackExchange.Redis/Enums/SetOperation.cs
@@ -1,4 +1,6 @@
-namespace StackExchange.Redis
+using System;
+
+namespace StackExchange.Redis
{
///
/// Describes an algebraic set operation that can be performed to combine multiple sets.
@@ -18,4 +20,18 @@ public enum SetOperation
///
Difference,
}
+
+ internal static class SetOperationExtensions
+ {
+ public static RedisCommand ToCommand(this SetOperation operation, bool store) => operation switch
+ {
+ SetOperation.Intersect when store => RedisCommand.ZINTERSTORE,
+ SetOperation.Intersect => RedisCommand.ZINTER,
+ SetOperation.Union when store => RedisCommand.ZUNIONSTORE,
+ SetOperation.Union => RedisCommand.ZUNION,
+ SetOperation.Difference when store => RedisCommand.ZDIFFSTORE,
+ SetOperation.Difference => RedisCommand.ZDIFF,
+ _ => throw new ArgumentOutOfRangeException(nameof(operation)),
+ };
+ }
}
diff --git a/src/StackExchange.Redis/Interfaces/IDatabase.cs b/src/StackExchange.Redis/Interfaces/IDatabase.cs
index c6887608f..02d530bbe 100644
--- a/src/StackExchange.Redis/Interfaces/IDatabase.cs
+++ b/src/StackExchange.Redis/Interfaces/IDatabase.cs
@@ -1355,9 +1355,42 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// https://redis.io/commands/zadd
long SortedSetAdd(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None);
+ ///
+ /// Computes a set operation for multiple sorted sets (optionally using per-set ),
+ /// optionally performing a specific aggregation (defaults to ).
+ /// cannot be used with weights or aggregation.
+ ///
+ /// The operation to perform.
+ /// The keys of the sorted sets.
+ /// The optional weights per set that correspond to .
+ /// The aggregation method (defaults to ).
+ /// The flags to use for this operation.
+ /// https://redis.io/commands/zunion
+ /// https://redis.io/commands/zinter
+ /// https://redis.io/commands/zdiff
+ /// The resulting sorted set.
+ RedisValue[] SortedSetCombine(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Computes a set operation for multiple sorted sets (optionally using per-set ),
+ /// optionally performing a specific aggregation (defaults to ).
+ /// cannot be used with weights or aggregation.
+ ///
+ /// The operation to perform.
+ /// The keys of the sorted sets.
+ /// The optional weights per set that correspond to .
+ /// The aggregation method (defaults to ).
+ /// The flags to use for this operation.
+ /// https://redis.io/commands/zunion
+ /// https://redis.io/commands/zinter
+ /// https://redis.io/commands/zdiff
+ /// The resulting sorted set with scores.
+ SortedSetEntry[] SortedSetCombineWithScores(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None);
+
///
/// Computes a set operation over two sorted sets, and stores the result in destination, optionally performing
/// a specific aggregation (defaults to sum).
+ /// cannot be used with aggregation.
///
/// The operation to perform.
/// The key to store the results in.
@@ -1367,12 +1400,14 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// The flags to use for this operation.
/// https://redis.io/commands/zunionstore
/// https://redis.io/commands/zinterstore
+ /// https://redis.io/commands/zdiffstore
/// The number of elements in the resulting sorted set at destination.
long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None);
///
/// Computes a set operation over multiple sorted sets (optionally using per-set weights), and stores the result in destination, optionally performing
/// a specific aggregation (defaults to sum).
+ /// cannot be used with aggregation.
///
/// The operation to perform.
/// The key to store the results in.
@@ -1382,6 +1417,7 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// The flags to use for this operation.
/// https://redis.io/commands/zunionstore
/// https://redis.io/commands/zinterstore
+ /// https://redis.io/commands/zdiffstore
/// The number of elements in the resulting sorted set at destination.
long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None);
@@ -1408,6 +1444,16 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// https://redis.io/commands/zincrby
double SortedSetIncrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None);
+ ///
+ /// Returns the cardinality of the intersection of the sorted sets at .
+ ///
+ /// The keys of the sorted sets.
+ /// If the intersection cardinality reaches partway through the computation, the algorithm will exit and yield as the cardinality (defaults to 0 meaning unlimited).
+ /// The flags to use for this operation.
+ /// The number of elements in the resulting intersection.
+ /// https://redis.io/commands/zintercard
+ long SortedSetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None);
+
///
/// Returns the sorted set cardinality (number of elements) of the sorted set stored at key.
///
diff --git a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
index 45074dd6d..e5febbcbd 100644
--- a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
+++ b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
@@ -1319,9 +1319,42 @@ public interface IDatabaseAsync : IRedisAsync
/// https://redis.io/commands/zadd
Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None);
+ ///
+ /// Computes a set operation for multiple sorted sets (optionally using per-set ),
+ /// optionally performing a specific aggregation (defaults to ).
+ /// cannot be used with weights or aggregation.
+ ///
+ /// The operation to perform.
+ /// The keys of the sorted sets.
+ /// The optional weights per set that correspond to .
+ /// The aggregation method (defaults to ).
+ /// The flags to use for this operation.
+ /// https://redis.io/commands/zunion
+ /// https://redis.io/commands/zinter
+ /// https://redis.io/commands/zdiff
+ /// The resulting sorted set.
+ Task SortedSetCombineAsync(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None);
+
+ ///
+ /// Computes a set operation for multiple sorted sets (optionally using per-set ),
+ /// optionally performing a specific aggregation (defaults to ).
+ /// cannot be used with weights or aggregation.
+ ///
+ /// The operation to perform.
+ /// The keys of the sorted sets.
+ /// The optional weights per set that correspond to .
+ /// The aggregation method (defaults to ).
+ /// The flags to use for this operation.
+ /// https://redis.io/commands/zunion
+ /// https://redis.io/commands/zinter
+ /// https://redis.io/commands/zdiff
+ /// The resulting sorted set with scores.
+ Task SortedSetCombineWithScoresAsync(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None);
+
///
/// Computes a set operation over two sorted sets, and stores the result in destination, optionally performing
/// a specific aggregation (defaults to sum).
+ /// cannot be used with aggregation.
///
/// The operation to perform.
/// The key to store the results in.
@@ -1331,12 +1364,14 @@ public interface IDatabaseAsync : IRedisAsync
/// The flags to use for this operation.
/// https://redis.io/commands/zunionstore
/// https://redis.io/commands/zinterstore
+ /// https://redis.io/commands/zdiffstore
/// The number of elements in the resulting sorted set at destination.
Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None);
///
/// Computes a set operation over multiple sorted sets (optionally using per-set weights), and stores the result in destination, optionally performing
/// a specific aggregation (defaults to sum).
+ /// cannot be used with aggregation.
///
/// The operation to perform.
/// The key to store the results in.
@@ -1346,6 +1381,7 @@ public interface IDatabaseAsync : IRedisAsync
/// The flags to use for this operation.
/// https://redis.io/commands/zunionstore
/// https://redis.io/commands/zinterstore
+ /// https://redis.io/commands/zdiffstore
/// The number of elements in the resulting sorted set at destination.
Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None);
@@ -1372,6 +1408,16 @@ public interface IDatabaseAsync : IRedisAsync
/// https://redis.io/commands/zincrby
Task SortedSetIncrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None);
+ ///
+ /// Returns the cardinality of the intersection of the sorted sets at .
+ ///
+ /// The keys of the sorted sets.
+ /// If the intersection cardinality reaches partway through the computation, the algorithm will exit and yield as the cardinality (defaults to 0 meaning unlimited).
+ /// The flags to use for this operation.
+ /// The number of elements in the resulting intersection.
+ /// https://redis.io/commands/zintercard
+ Task SortedSetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None);
+
///
/// Returns the sorted set cardinality (number of elements) of the sorted set stored at key.
///
diff --git a/src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs b/src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs
index 9563d4d13..c2e79b1d0 100644
--- a/src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs
+++ b/src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs
@@ -345,6 +345,12 @@ public bool SortedSetAdd(RedisKey key, RedisValue member, double score, CommandF
public bool SortedSetAdd(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
Inner.SortedSetAdd(ToInner(key), member, score, when, flags);
+ public RedisValue[] SortedSetCombine(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) =>
+ Inner.SortedSetCombine(operation, keys, weights, aggregate, flags);
+
+ public SortedSetEntry[] SortedSetCombineWithScores(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) =>
+ Inner.SortedSetCombineWithScores(operation, keys, weights, aggregate, flags);
+
public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) =>
Inner.SortedSetCombineAndStore(operation, ToInner(destination), ToInner(keys), weights, aggregate, flags);
@@ -357,6 +363,9 @@ public double SortedSetDecrement(RedisKey key, RedisValue member, double value,
public double SortedSetIncrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) =>
Inner.SortedSetIncrement(ToInner(key), member, value, flags);
+ public long SortedSetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) =>
+ Inner.SortedSetIntersectionLength(keys, limit, flags);
+
public long SortedSetLength(RedisKey key, double min = -1.0 / 0.0, double max = 1.0 / 0.0, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) =>
Inner.SortedSetLength(ToInner(key), min, max, exclude, flags);
diff --git a/src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs b/src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs
index 87f40b608..a8fa91649 100644
--- a/src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs
+++ b/src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs
@@ -358,6 +358,12 @@ public Task SortedSetAddAsync(RedisKey key, RedisValue member, double scor
public Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
Inner.SortedSetAddAsync(ToInner(key), member, score, when, flags);
+ public Task SortedSetCombineAsync(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) =>
+ Inner.SortedSetCombineAsync(operation, keys, weights, aggregate, flags);
+
+ public Task SortedSetCombineWithScoresAsync(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) =>
+ Inner.SortedSetCombineWithScoresAsync(operation, keys, weights, aggregate, flags);
+
public Task SortedSetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) =>
Inner.SortedSetCombineAndStoreAsync(operation, ToInner(destination), ToInner(keys), weights, aggregate, flags);
@@ -370,6 +376,9 @@ public Task SortedSetDecrementAsync(RedisKey key, RedisValue member, dou
public Task SortedSetIncrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) =>
Inner.SortedSetIncrementAsync(ToInner(key), member, value, flags);
+ public Task SortedSetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) =>
+ Inner.SortedSetIntersectionLengthAsync(keys, limit, flags);
+
public Task SortedSetLengthAsync(RedisKey key, double min = -1.0 / 0.0, double max = 1.0 / 0.0, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) =>
Inner.SortedSetLengthAsync(ToInner(key), min, max, exclude, flags);
diff --git a/src/StackExchange.Redis/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI.Shipped.txt
index 4f07e98e2..ade9fdd5f 100644
--- a/src/StackExchange.Redis/PublicAPI.Shipped.txt
+++ b/src/StackExchange.Redis/PublicAPI.Shipped.txt
@@ -592,10 +592,13 @@ StackExchange.Redis.IDatabase.SortedSetAdd(StackExchange.Redis.RedisKey key, Sta
StackExchange.Redis.IDatabase.SortedSetAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double score, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool
StackExchange.Redis.IDatabase.SortedSetAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.SortedSetEntry[]! values, StackExchange.Redis.CommandFlags flags) -> long
StackExchange.Redis.IDatabase.SortedSetAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.SortedSetEntry[]! values, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
+StackExchange.Redis.IDatabase.SortedSetCombine(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey[]! keys, double[]? weights = null, StackExchange.Redis.Aggregate aggregate = StackExchange.Redis.Aggregate.Sum, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]!
+StackExchange.Redis.IDatabase.SortedSetCombineWithScores(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey[]! keys, double[]? weights = null, StackExchange.Redis.Aggregate aggregate = StackExchange.Redis.Aggregate.Sum, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.SortedSetEntry[]!
StackExchange.Redis.IDatabase.SortedSetCombineAndStore(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.Aggregate aggregate = StackExchange.Redis.Aggregate.Sum, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IDatabase.SortedSetCombineAndStore(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey[]! keys, double[]? weights = null, StackExchange.Redis.Aggregate aggregate = StackExchange.Redis.Aggregate.Sum, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IDatabase.SortedSetDecrement(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> double
StackExchange.Redis.IDatabase.SortedSetIncrement(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> double
+StackExchange.Redis.IDatabase.SortedSetIntersectionLength(StackExchange.Redis.RedisKey[]! keys, long limit = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IDatabase.SortedSetLength(StackExchange.Redis.RedisKey key, double min = -Infinity, double max = Infinity, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IDatabase.SortedSetLengthByValue(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue min, StackExchange.Redis.RedisValue max, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IDatabase.SortedSetPop(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.SortedSetEntry[]!
@@ -783,10 +786,13 @@ StackExchange.Redis.IDatabaseAsync.SortedSetAddAsync(StackExchange.Redis.RedisKe
StackExchange.Redis.IDatabaseAsync.SortedSetAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double score, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.SortedSetAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.SortedSetEntry[]! values, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.SortedSetAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.SortedSetEntry[]! values, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+StackExchange.Redis.IDatabaseAsync.SortedSetCombineAsync(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey[]! keys, double[]? weights = null, StackExchange.Redis.Aggregate aggregate = StackExchange.Redis.Aggregate.Sum, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+StackExchange.Redis.IDatabaseAsync.SortedSetCombineWithScoresAsync(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey[]! keys, double[]? weights = null, StackExchange.Redis.Aggregate aggregate = StackExchange.Redis.Aggregate.Sum, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.SortedSetCombineAndStoreAsync(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey first, StackExchange.Redis.RedisKey second, StackExchange.Redis.Aggregate aggregate = StackExchange.Redis.Aggregate.Sum, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.SortedSetCombineAndStoreAsync(StackExchange.Redis.SetOperation operation, StackExchange.Redis.RedisKey destination, StackExchange.Redis.RedisKey[]! keys, double[]? weights = null, StackExchange.Redis.Aggregate aggregate = StackExchange.Redis.Aggregate.Sum, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.SortedSetDecrementAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.SortedSetIncrementAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue member, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
+StackExchange.Redis.IDatabaseAsync.SortedSetIntersectionLengthAsync(StackExchange.Redis.RedisKey[]! keys, long limit = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.SortedSetLengthAsync(StackExchange.Redis.RedisKey key, double min = -Infinity, double max = Infinity, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.SortedSetLengthByValueAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue min, StackExchange.Redis.RedisValue max, StackExchange.Redis.Exclude exclude = StackExchange.Redis.Exclude.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.SortedSetPopAsync(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.Order order = StackExchange.Redis.Order.Ascending, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs
index 54181f982..269211ee1 100644
--- a/src/StackExchange.Redis/RedisDatabase.cs
+++ b/src/StackExchange.Redis/RedisDatabase.cs
@@ -1616,6 +1616,30 @@ public Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, When
return ExecuteAsync(msg, ResultProcessor.Int64);
}
+ public RedisValue[] SortedSetCombine(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = GetSortedSetCombineCommandMessage(operation, keys, weights, aggregate, withScores: false, flags);
+ return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty());
+ }
+
+ public Task SortedSetCombineAsync(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = GetSortedSetCombineCommandMessage(operation, keys, weights, aggregate, withScores: false, flags);
+ return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty());
+ }
+
+ public SortedSetEntry[] SortedSetCombineWithScores(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = GetSortedSetCombineCommandMessage(operation, keys, weights, aggregate, withScores: true, flags);
+ return ExecuteSync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty());
+ }
+
+ public Task SortedSetCombineWithScoresAsync(SetOperation operation, RedisKey[] keys, double[]? weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = GetSortedSetCombineCommandMessage(operation, keys, weights, aggregate, withScores: true, flags);
+ return ExecuteAsync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty());
+ }
+
public long SortedSetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None)
{
var msg = GetSortedSetCombineAndStoreCommandMessage(operation, destination, new[] { first, second }, null, aggregate, flags);
@@ -1662,6 +1686,18 @@ public Task SortedSetIncrementAsync(RedisKey key, RedisValue member, dou
return ExecuteAsync(msg, ResultProcessor.Double);
}
+ public long SortedSetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = GetSortedSetIntersectionLengthMessage(keys, limit, flags);
+ return ExecuteSync(msg, ResultProcessor.Int64);
+ }
+
+ public Task SortedSetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None)
+ {
+ var msg = GetSortedSetIntersectionLengthMessage(keys, limit, flags);
+ return ExecuteAsync(msg, ResultProcessor.Int64);
+ }
+
public long SortedSetLength(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None)
{
var msg = GetSortedSetLengthMessage(key, min, max, exclude, flags);
@@ -3168,36 +3204,91 @@ private Message GetSortedSetAddMessage(RedisKey destination, RedisKey key, long
private Message GetSortedSetCombineAndStoreCommandMessage(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights, Aggregate aggregate, CommandFlags flags)
{
- var command = operation switch
+ var command = operation.ToCommand(store: true);
+ if (keys == null)
{
- SetOperation.Intersect => RedisCommand.ZINTERSTORE,
- SetOperation.Union => RedisCommand.ZUNIONSTORE,
- _ => throw new ArgumentOutOfRangeException(nameof(operation)),
- };
- if (keys == null) throw new ArgumentNullException(nameof(keys));
+ throw new ArgumentNullException(nameof(keys));
+ }
+ if (command == RedisCommand.ZDIFFSTORE && (weights != null || aggregate != Aggregate.Sum))
+ {
+ throw new ArgumentException("ZDIFFSTORE cannot be used with weights or aggregation.");
+ }
+ if (weights != null && keys.Length != weights.Length)
+ {
+ throw new ArgumentException("Keys and weights should have the same number of elements.", nameof(weights));
+ }
- List? values = null;
- if (weights != null && weights.Length != 0)
+ RedisValue[] values = RedisValue.EmptyArray;
+
+ var argsLength = (weights?.Length > 0 ? 1 + weights.Length : 0) + (aggregate != Aggregate.Sum ? 2 : 0);
+ if (argsLength > 0)
{
- (values ??= new List()).Add(RedisLiterals.WEIGHTS);
+ values = new RedisValue[argsLength];
+ AddWeightsAggregationAndScore(values, weights, aggregate);
+ }
+ return new SortedSetCombineAndStoreCommandMessage(Database, flags, command, destination, keys, values);
+ }
+
+ private Message GetSortedSetCombineCommandMessage(SetOperation operation, RedisKey[] keys, double[]? weights, Aggregate aggregate, bool withScores, CommandFlags flags)
+ {
+ var command = operation.ToCommand(store: false);
+ if (keys == null)
+ {
+ throw new ArgumentNullException(nameof(keys));
+ }
+ if (command == RedisCommand.ZDIFF && (weights != null || aggregate != Aggregate.Sum))
+ {
+ throw new ArgumentException("ZDIFF cannot be used with weights or aggregation.");
+ }
+ if (weights != null && keys.Length != weights.Length)
+ {
+ throw new ArgumentException("Keys and weights should have the same number of elements.", nameof(weights));
+ }
+
+ var i = 0;
+ var values = new RedisValue[1 + keys.Length +
+ (weights?.Length > 0 ? 1 + weights.Length : 0) +
+ (aggregate != Aggregate.Sum ? 2 : 0) +
+ (withScores ? 1 : 0)];
+ values[i++] = keys.Length;
+ foreach (var key in keys)
+ {
+ values[i++] = key.AsRedisValue();
+ }
+ AddWeightsAggregationAndScore(values.AsSpan(i), weights, aggregate, withScores: withScores);
+ return Message.Create(Database, flags, command, values ?? RedisValue.EmptyArray);
+ }
+
+ private void AddWeightsAggregationAndScore(Span values, double[]? weights, Aggregate aggregate, bool withScores = false)
+ {
+ int i = 0;
+ if (weights?.Length > 0)
+ {
+ values[i++] = RedisLiterals.WEIGHTS;
foreach (var weight in weights)
- values.Add(weight);
+ {
+ values[i++] = weight;
+ }
}
switch (aggregate)
{
- case Aggregate.Sum: break; // default
+ case Aggregate.Sum:
+ break; // add nothing - Redis default
case Aggregate.Min:
- (values ??= new List()).Add(RedisLiterals.AGGREGATE);
- values.Add(RedisLiterals.MIN);
+ values[i++] = RedisLiterals.AGGREGATE;
+ values[i++] = RedisLiterals.MIN;
break;
case Aggregate.Max:
- (values ??= new List()).Add(RedisLiterals.AGGREGATE);
- values.Add(RedisLiterals.MAX);
+ values[i++] = RedisLiterals.AGGREGATE;
+ values[i++] = RedisLiterals.MAX;
break;
default:
throw new ArgumentOutOfRangeException(nameof(aggregate));
}
- return new SortedSetCombineAndStoreCommandMessage(Database, flags, command, destination, keys, values?.ToArray() ?? RedisValue.EmptyArray);
+ if (withScores)
+ {
+ values[i++] = RedisLiterals.WITHSCORES;
+ }
}
private Message GetSortedSetLengthMessage(RedisKey key, double min, double max, Exclude exclude, CommandFlags flags)
@@ -3210,6 +3301,25 @@ private Message GetSortedSetLengthMessage(RedisKey key, double min, double max,
return Message.Create(Database, flags, RedisCommand.ZCOUNT, key, from, to);
}
+ private Message GetSortedSetIntersectionLengthMessage(RedisKey[] keys, long limit, CommandFlags flags)
+ {
+ if (keys == null) throw new ArgumentNullException(nameof(keys));
+
+ var i = 0;
+ var values = new RedisValue[1 + keys.Length + (limit > 0 ? 2 : 0)];
+ values[i++] = keys.Length;
+ foreach (var key in keys)
+ {
+ values[i++] = key.AsRedisValue();
+ }
+ if (limit > 0)
+ {
+ values[i++] = RedisLiterals.LIMIT;
+ values[i++] = limit;
+ }
+ return Message.Create(Database, flags, RedisCommand.ZINTERCARD, values);
+ }
+
private Message GetSortedSetRangeByScoreMessage(RedisKey key, double start, double stop, Exclude exclude, Order order, long skip, long take, CommandFlags flags, bool withScores)
{
// usage: {ZRANGEBYSCORE|ZREVRANGEBYSCORE} key from to [WITHSCORES] [LIMIT offset count]
diff --git a/tests/StackExchange.Redis.Tests/DatabaseWrapperTests.cs b/tests/StackExchange.Redis.Tests/DatabaseWrapperTests.cs
index 78db3c07a..f8ca3f325 100644
--- a/tests/StackExchange.Redis.Tests/DatabaseWrapperTests.cs
+++ b/tests/StackExchange.Redis.Tests/DatabaseWrapperTests.cs
@@ -741,6 +741,22 @@ public void SortedSetAdd_2()
mock.Verify(_ => _.SortedSetAdd("prefix:key", values, When.Exists, CommandFlags.None));
}
+ [Fact]
+ public void SortedSetCombine()
+ {
+ RedisKey[] keys = new RedisKey[] { "a", "b" };
+ wrapper.SortedSetCombine(SetOperation.Intersect, keys);
+ mock.Verify(_ => _.SortedSetCombine(SetOperation.Intersect, keys, null, Aggregate.Sum, CommandFlags.None));
+ }
+
+ [Fact]
+ public void SortedSetCombineWithScores()
+ {
+ RedisKey[] keys = new RedisKey[] { "a", "b" };
+ wrapper.SortedSetCombineWithScores(SetOperation.Intersect, keys);
+ mock.Verify(_ => _.SortedSetCombineWithScores(SetOperation.Intersect, keys, null, Aggregate.Sum, CommandFlags.None));
+ }
+
[Fact]
public void SortedSetCombineAndStore_1()
{
@@ -771,6 +787,14 @@ public void SortedSetIncrement()
mock.Verify(_ => _.SortedSetIncrement("prefix:key", "member", 1.23, CommandFlags.None));
}
+ [Fact]
+ public void SortedSetIntersectionLength()
+ {
+ RedisKey[] keys = new RedisKey[] { "a", "b" };
+ wrapper.SortedSetIntersectionLength(keys, 1, CommandFlags.None);
+ mock.Verify(_ => _.SortedSetIntersectionLength(keys, 1, CommandFlags.None));
+ }
+
[Fact]
public void SortedSetLength()
{
diff --git a/tests/StackExchange.Redis.Tests/SortedSets.cs b/tests/StackExchange.Redis.Tests/SortedSets.cs
index e7cdcc59c..fec0d82ac 100644
--- a/tests/StackExchange.Redis.Tests/SortedSets.cs
+++ b/tests/StackExchange.Redis.Tests/SortedSets.cs
@@ -38,6 +38,15 @@ public SortedSets(ITestOutputHelper output, SharedConnectionFixture fixture) : b
new SortedSetEntry("j", 512)
};
+ private static readonly SortedSetEntry[] entriesPow3 = new SortedSetEntry[]
+ {
+ new SortedSetEntry("a", 1),
+ new SortedSetEntry("c", 4),
+ new SortedSetEntry("e", 16),
+ new SortedSetEntry("g", 64),
+ new SortedSetEntry("i", 256),
+ };
+
private static readonly SortedSetEntry[] lexEntries = new SortedSetEntry[]
{
new SortedSetEntry("a", 0),
@@ -52,6 +61,284 @@ public SortedSets(ITestOutputHelper output, SharedConnectionFixture fixture) : b
new SortedSetEntry("j", 0)
};
+ [Fact]
+ public void SortedSetCombine()
+ {
+ using var conn = Create();
+ Skip.IfBelow(conn, RedisFeatures.v6_2_0);
+
+ var db = conn.GetDatabase();
+ var key1 = Me();
+ db.KeyDelete(key1, CommandFlags.FireAndForget);
+ var key2 = Me() + "2";
+ db.KeyDelete(key2, CommandFlags.FireAndForget);
+
+ db.SortedSetAdd(key1, entries);
+ db.SortedSetAdd(key2, entriesPow3);
+
+ var diff = db.SortedSetCombine(SetOperation.Difference, new RedisKey[]{ key1, key2});
+ Assert.Equal(5, diff.Length);
+ Assert.Equal("b", diff[0]);
+
+ var inter = db.SortedSetCombine(SetOperation.Intersect, new RedisKey[]{ key1, key2});
+ Assert.Equal(5, inter.Length);
+ Assert.Equal("a", inter[0]);
+
+ var union = db.SortedSetCombine(SetOperation.Union, new RedisKey[]{ key1, key2});
+ Assert.Equal(10, union.Length);
+ Assert.Equal("a", union[0]);
+ }
+
+
+ [Fact]
+ public async Task SortedSetCombineAsync()
+ {
+ using var conn = Create();
+ Skip.IfBelow(conn, RedisFeatures.v6_2_0);
+
+ var db = conn.GetDatabase();
+ var key1 = Me();
+ db.KeyDelete(key1, CommandFlags.FireAndForget);
+ var key2 = Me() + "2";
+ db.KeyDelete(key2, CommandFlags.FireAndForget);
+
+ db.SortedSetAdd(key1, entries);
+ db.SortedSetAdd(key2, entriesPow3);
+
+ var diff = await db.SortedSetCombineAsync(SetOperation.Difference, new RedisKey[] { key1, key2 });
+ Assert.Equal(5, diff.Length);
+ Assert.Equal("b", diff[0]);
+
+ var inter = await db.SortedSetCombineAsync(SetOperation.Intersect, new RedisKey[] { key1, key2 });
+ Assert.Equal(5, inter.Length);
+ Assert.Equal("a", inter[0]);
+
+ var union = await db.SortedSetCombineAsync(SetOperation.Union, new RedisKey[] { key1, key2 });
+ Assert.Equal(10, union.Length);
+ Assert.Equal("a", union[0]);
+ }
+
+ [Fact]
+ public void SortedSetCombineWithScores()
+ {
+ using var conn = Create();
+ Skip.IfBelow(conn, RedisFeatures.v6_2_0);
+
+ var db = conn.GetDatabase();
+ var key1 = Me();
+ db.KeyDelete(key1, CommandFlags.FireAndForget);
+ var key2 = Me() + "2";
+ db.KeyDelete(key2, CommandFlags.FireAndForget);
+
+ db.SortedSetAdd(key1, entries);
+ db.SortedSetAdd(key2, entriesPow3);
+
+ var diff = db.SortedSetCombineWithScores(SetOperation.Difference, new RedisKey[]{ key1, key2});
+ Assert.Equal(5, diff.Length);
+ Assert.Equal(new SortedSetEntry("b", 2), diff[0]);
+
+ var inter = db.SortedSetCombineWithScores(SetOperation.Intersect, new RedisKey[]{ key1, key2});
+ Assert.Equal(5, inter.Length);
+ Assert.Equal(new SortedSetEntry("a", 2), inter[0]);
+
+ var union = db.SortedSetCombineWithScores(SetOperation.Union, new RedisKey[]{ key1, key2});
+ Assert.Equal(10, union.Length);
+ Assert.Equal(new SortedSetEntry("a", 2), union[0]);
+ }
+
+
+ [Fact]
+ public async Task SortedSetCombineWithScoresAsync()
+ {
+ using var conn = Create();
+ Skip.IfBelow(conn, RedisFeatures.v6_2_0);
+
+ var db = conn.GetDatabase();
+ var key1 = Me();
+ db.KeyDelete(key1, CommandFlags.FireAndForget);
+ var key2 = Me() + "2";
+ db.KeyDelete(key2, CommandFlags.FireAndForget);
+
+ db.SortedSetAdd(key1, entries);
+ db.SortedSetAdd(key2, entriesPow3);
+
+ var diff = await db.SortedSetCombineWithScoresAsync(SetOperation.Difference, new RedisKey[] { key1, key2 });
+ Assert.Equal(5, diff.Length);
+ Assert.Equal(new SortedSetEntry("b", 2), diff[0]);
+
+ var inter = await db.SortedSetCombineWithScoresAsync(SetOperation.Intersect, new RedisKey[] { key1, key2 });
+ Assert.Equal(5, inter.Length);
+ Assert.Equal(new SortedSetEntry("a", 2), inter[0]);
+
+ var union = await db.SortedSetCombineWithScoresAsync(SetOperation.Union, new RedisKey[] { key1, key2 });
+ Assert.Equal(10, union.Length);
+ Assert.Equal(new SortedSetEntry("a", 2), union[0]);
+ }
+
+ [Fact]
+ public void SortedSetCombineAndStore()
+ {
+ using var conn = Create();
+ Skip.IfBelow(conn, RedisFeatures.v6_2_0);
+
+ var db = conn.GetDatabase();
+ var key1 = Me();
+ db.KeyDelete(key1, CommandFlags.FireAndForget);
+ var key2 = Me() + "2";
+ db.KeyDelete(key2, CommandFlags.FireAndForget);
+ var destination = Me() + "dest";
+ db.KeyDelete(destination, CommandFlags.FireAndForget);
+
+ db.SortedSetAdd(key1, entries);
+ db.SortedSetAdd(key2, entriesPow3);
+
+ var diff = db.SortedSetCombineAndStore(SetOperation.Difference, destination, new RedisKey[]{ key1, key2});
+ Assert.Equal(5, diff);
+
+ var inter = db.SortedSetCombineAndStore(SetOperation.Intersect, destination, new RedisKey[]{ key1, key2});
+ Assert.Equal(5, inter);
+
+ var union = db.SortedSetCombineAndStore(SetOperation.Union, destination, new RedisKey[]{ key1, key2});
+ Assert.Equal(10, union);
+ }
+
+
+ [Fact]
+ public async Task SortedSetCombineAndStoreAsync()
+ {
+ using var conn = Create();
+ Skip.IfBelow(conn, RedisFeatures.v6_2_0);
+
+ var db = conn.GetDatabase();
+ var key1 = Me();
+ db.KeyDelete(key1, CommandFlags.FireAndForget);
+ var key2 = Me() + "2";
+ db.KeyDelete(key2, CommandFlags.FireAndForget);
+ var destination = Me() + "dest";
+ db.KeyDelete(destination, CommandFlags.FireAndForget);
+
+ db.SortedSetAdd(key1, entries);
+ db.SortedSetAdd(key2, entriesPow3);
+
+ var diff = await db.SortedSetCombineAndStoreAsync(SetOperation.Difference, destination, new RedisKey[] { key1, key2 });
+ Assert.Equal(5, diff);
+
+ var inter = await db.SortedSetCombineAndStoreAsync(SetOperation.Intersect, destination, new RedisKey[] { key1, key2 });
+ Assert.Equal(5, inter);
+
+ var union = await db.SortedSetCombineAndStoreAsync(SetOperation.Union, destination, new RedisKey[] { key1, key2 });
+ Assert.Equal(10, union);
+ }
+
+ [Fact]
+ public async Task SortedSetCombineErrors()
+ {
+ using var conn = Create();
+ Skip.IfBelow(conn, RedisFeatures.v6_2_0);
+
+ var db = conn.GetDatabase();
+ var key1 = Me();
+ db.KeyDelete(key1, CommandFlags.FireAndForget);
+ var key2 = Me() + "2";
+ db.KeyDelete(key2, CommandFlags.FireAndForget);
+ var destination = Me() + "dest";
+ db.KeyDelete(destination, CommandFlags.FireAndForget);
+
+ db.SortedSetAdd(key1, entries);
+ db.SortedSetAdd(key2, entriesPow3);
+
+ // ZDIFF can't be used with weights
+ var ex = Assert.Throws(() => db.SortedSetCombine(SetOperation.Difference, new RedisKey[] { key1, key2 }, new double[] { 1, 2 }));
+ Assert.Equal("ZDIFF cannot be used with weights or aggregation.", ex.Message);
+ ex = Assert.Throws(() => db.SortedSetCombineWithScores(SetOperation.Difference, new RedisKey[] { key1, key2 }, new double[] { 1, 2 }));
+ Assert.Equal("ZDIFF cannot be used with weights or aggregation.", ex.Message);
+ ex = Assert.Throws(() => db.SortedSetCombineAndStore(SetOperation.Difference, destination, new RedisKey[] { key1, key2 }, new double[] { 1, 2 }));
+ Assert.Equal("ZDIFFSTORE cannot be used with weights or aggregation.", ex.Message);
+ // and Async...
+ ex = await Assert.ThrowsAsync(() => db.SortedSetCombineAsync(SetOperation.Difference, new RedisKey[] { key1, key2 }, new double[] { 1, 2 }));
+ Assert.Equal("ZDIFF cannot be used with weights or aggregation.", ex.Message);
+ ex = await Assert.ThrowsAsync(() => db.SortedSetCombineWithScoresAsync(SetOperation.Difference, new RedisKey[] { key1, key2 }, new double[] { 1, 2 }));
+ Assert.Equal("ZDIFF cannot be used with weights or aggregation.", ex.Message);
+ ex = await Assert.ThrowsAsync(() => db.SortedSetCombineAndStoreAsync(SetOperation.Difference, destination, new RedisKey[] { key1, key2 }, new double[] { 1, 2 }));
+ Assert.Equal("ZDIFFSTORE cannot be used with weights or aggregation.", ex.Message);
+
+ // ZDIFF can't be used with aggregation
+ ex = Assert.Throws(() => db.SortedSetCombine(SetOperation.Difference, new RedisKey[] { key1, key2 }, aggregate: Aggregate.Max));
+ Assert.Equal("ZDIFF cannot be used with weights or aggregation.", ex.Message);
+ ex = Assert.Throws(() => db.SortedSetCombineWithScores(SetOperation.Difference, new RedisKey[] { key1, key2 }, aggregate: Aggregate.Max));
+ Assert.Equal("ZDIFF cannot be used with weights or aggregation.", ex.Message);
+ ex = Assert.Throws(() => db.SortedSetCombineAndStore(SetOperation.Difference, destination, new RedisKey[] { key1, key2 }, aggregate: Aggregate.Max));
+ Assert.Equal("ZDIFFSTORE cannot be used with weights or aggregation.", ex.Message);
+ // and Async...
+ ex = await Assert.ThrowsAsync(() => db.SortedSetCombineAsync(SetOperation.Difference, new RedisKey[] { key1, key2 }, aggregate: Aggregate.Max));
+ Assert.Equal("ZDIFF cannot be used with weights or aggregation.", ex.Message);
+ ex = await Assert.ThrowsAsync(() => db.SortedSetCombineWithScoresAsync(SetOperation.Difference, new RedisKey[] { key1, key2 }, aggregate: Aggregate.Max));
+ Assert.Equal("ZDIFF cannot be used with weights or aggregation.", ex.Message);
+ ex = await Assert.ThrowsAsync(() => db.SortedSetCombineAndStoreAsync(SetOperation.Difference, destination, new RedisKey[] { key1, key2 }, aggregate: Aggregate.Max));
+ Assert.Equal("ZDIFFSTORE cannot be used with weights or aggregation.", ex.Message);
+
+ // Too many weights
+ ex = Assert.Throws(() => db.SortedSetCombine(SetOperation.Union, new RedisKey[] { key1, key2 }, new double[] { 1, 2, 3 }));
+ Assert.StartsWith("Keys and weights should have the same number of elements.", ex.Message);
+ ex = Assert.Throws(() => db.SortedSetCombineWithScores(SetOperation.Union, new RedisKey[] { key1, key2 }, new double[] { 1, 2, 3 }));
+ Assert.StartsWith("Keys and weights should have the same number of elements.", ex.Message);
+ ex = Assert.Throws(() => db.SortedSetCombineAndStore(SetOperation.Union, destination, new RedisKey[] { key1, key2 }, new double[] { 1, 2, 3 }));
+ Assert.StartsWith("Keys and weights should have the same number of elements.", ex.Message);
+ // and Async...
+ ex = await Assert.ThrowsAsync(() => db.SortedSetCombineAsync(SetOperation.Union, new RedisKey[] { key1, key2 }, new double[] { 1, 2, 3 }));
+ Assert.StartsWith("Keys and weights should have the same number of elements.", ex.Message);
+ ex = await Assert.ThrowsAsync(() => db.SortedSetCombineWithScoresAsync(SetOperation.Union, new RedisKey[] { key1, key2 }, new double[] { 1, 2, 3 }));
+ Assert.StartsWith("Keys and weights should have the same number of elements.", ex.Message);
+ ex = await Assert.ThrowsAsync(() => db.SortedSetCombineAndStoreAsync(SetOperation.Union, destination, new RedisKey[] { key1, key2 }, new double[] { 1, 2, 3 }));
+ Assert.StartsWith("Keys and weights should have the same number of elements.", ex.Message);
+ }
+
+ [Fact]
+ public void SortedSetIntersectionLength()
+ {
+ using var conn = Create();
+ Skip.IfBelow(conn, RedisFeatures.v7_0_0_rc1);
+
+ var db = conn.GetDatabase();
+ var key1 = Me();
+ db.KeyDelete(key1, CommandFlags.FireAndForget);
+ var key2 = Me() + "2";
+ db.KeyDelete(key2, CommandFlags.FireAndForget);
+
+ db.SortedSetAdd(key1, entries);
+ db.SortedSetAdd(key2, entriesPow3);
+
+ var inter = db.SortedSetIntersectionLength(new RedisKey[]{ key1, key2});
+ Assert.Equal(5, inter);
+
+ // with limit
+ inter = db.SortedSetIntersectionLength(new RedisKey[]{ key1, key2}, 3);
+ Assert.Equal(3, inter);
+ }
+
+ [Fact]
+ public async Task SortedSetIntersectionLengthAsync()
+ {
+ using var conn = Create();
+ Skip.IfBelow(conn, RedisFeatures.v7_0_0_rc1);
+
+ var db = conn.GetDatabase();
+ var key1 = Me();
+ db.KeyDelete(key1, CommandFlags.FireAndForget);
+ var key2 = Me() + "2";
+ db.KeyDelete(key2, CommandFlags.FireAndForget);
+
+ db.SortedSetAdd(key1, entries);
+ db.SortedSetAdd(key2, entriesPow3);
+
+ var inter = await db.SortedSetIntersectionLengthAsync(new RedisKey[] { key1, key2 });
+ Assert.Equal(5, inter);
+
+ // with limit
+ inter = await db.SortedSetIntersectionLengthAsync(new RedisKey[] { key1, key2 }, 3);
+ Assert.Equal(3, inter);
+ }
+
[Fact]
public void SortedSetPopMulti_Multi()
{
diff --git a/tests/StackExchange.Redis.Tests/WrapperBaseTests.cs b/tests/StackExchange.Redis.Tests/WrapperBaseTests.cs
index 322f71a3f..c8d83b309 100644
--- a/tests/StackExchange.Redis.Tests/WrapperBaseTests.cs
+++ b/tests/StackExchange.Redis.Tests/WrapperBaseTests.cs
@@ -687,6 +687,22 @@ public void SortedSetAddAsync_2()
mock.Verify(_ => _.SortedSetAddAsync("prefix:key", values, When.Exists, CommandFlags.None));
}
+ [Fact]
+ public void SortedSetCombineAsync()
+ {
+ RedisKey[] keys = new RedisKey[] { "a", "b" };
+ wrapper.SortedSetCombineAsync(SetOperation.Intersect, keys);
+ mock.Verify(_ => _.SortedSetCombineAsync(SetOperation.Intersect, keys, null, Aggregate.Sum, CommandFlags.None));
+ }
+
+ [Fact]
+ public void SortedSetCombineWithScoresAsync()
+ {
+ RedisKey[] keys = new RedisKey[] { "a", "b" };
+ wrapper.SortedSetCombineWithScoresAsync(SetOperation.Intersect, keys);
+ mock.Verify(_ => _.SortedSetCombineWithScoresAsync(SetOperation.Intersect, keys, null, Aggregate.Sum, CommandFlags.None));
+ }
+
[Fact]
public void SortedSetCombineAndStoreAsync_1()
{
@@ -717,6 +733,14 @@ public void SortedSetIncrementAsync()
mock.Verify(_ => _.SortedSetIncrementAsync("prefix:key", "member", 1.23, CommandFlags.None));
}
+ [Fact]
+ public void SortedSetIntersectionLengthAsync()
+ {
+ RedisKey[] keys = new RedisKey[] { "a", "b" };
+ wrapper.SortedSetIntersectionLengthAsync(keys, 1, CommandFlags.None);
+ mock.Verify(_ => _.SortedSetIntersectionLengthAsync(keys, 1, CommandFlags.None));
+ }
+
[Fact]
public void SortedSetLengthAsync()
{