From 700bee8267f0a41f8f5384ef2afd2c7b1a7b392a Mon Sep 17 00:00:00 2001 From: John Loehrer Date: Thu, 22 Aug 2024 12:30:35 -0700 Subject: [PATCH] add support for remaining sorted-set commands #31 --- .../RedisSortedSetAsyncCommands.scala | 526 +++++++++++++++--- .../LettuceRedisSortedSetAsyncCommands.scala | 154 +++++ .../RedisCommandsIntegrationSpec.scala | 70 ++- ...ttuceRedisSortedSetAsyncCommandsSpec.scala | 326 ++++++++++- 4 files changed, 988 insertions(+), 88 deletions(-) diff --git a/modules/api/src/main/scala/com/github/scoquelin/arugula/commands/RedisSortedSetAsyncCommands.scala b/modules/api/src/main/scala/com/github/scoquelin/arugula/commands/RedisSortedSetAsyncCommands.scala index da2f361..d1c37eb 100644 --- a/modules/api/src/main/scala/com/github/scoquelin/arugula/commands/RedisSortedSetAsyncCommands.scala +++ b/modules/api/src/main/scala/com/github/scoquelin/arugula/commands/RedisSortedSetAsyncCommands.scala @@ -8,51 +8,58 @@ import com.github.scoquelin.arugula.commands.RedisBaseAsyncCommands.{InitialCurs /** * Asynchronous commands for manipulating/querying Sorted Sets + * * @tparam K The key type * @tparam V The value type */ trait RedisSortedSetAsyncCommands[K, V] { + import RedisSortedSetAsyncCommands._ /** * Remove and return the member with the lowest score from one or more sorted sets - * @param timeout The timeout + * + * @param timeout The timeout * @param direction Which end of the sorted set to pop from, the min or max - * @param keys The keys + * @param keys The keys * @return The member removed based on the pop direction */ - def bzMPop(timeout: FiniteDuration, direction: SortOrder, keys: K*): Future[Option[ScoreWithKeyValue[K,V]]] + def bzMPop(timeout: FiniteDuration, direction: SortOrder, keys: K*): Future[Option[ScoreWithKeyValue[K, V]]] /** * Remove and return up to count members from the end of one or more sorted sets based on the pop direction (min or max) - * @param timeout The timeout - * @param count The number of members to pop + * + * @param timeout The timeout + * @param count The number of members to pop * @param direction Which end of the sorted set to pop from, the min or max - * @param keys The keys + * @param keys The keys * @return The members removed based on the pop direction */ - def bzMPop(timeout: FiniteDuration, count: Int, direction: SortOrder, keys: K*): Future[List[ScoreWithKeyValue[K,V]]] + def bzMPop(timeout: FiniteDuration, count: Int, direction: SortOrder, keys: K*): Future[List[ScoreWithKeyValue[K, V]]] /** * Remove and return the member with the lowest score from one or more sorted sets + * * @param timeout The timeout - * @param keys The keys + * @param keys The keys * @return The member with the lowest score, or None if the sets are empty */ - def bzPopMin(timeout: FiniteDuration, keys: K*): Future[Option[ScoreWithKeyValue[K,V]]] + def bzPopMin(timeout: FiniteDuration, keys: K*): Future[Option[ScoreWithKeyValue[K, V]]] /** * Remove and return the member with the highest score from one or more sorted sets + * * @param timeout The timeout - * @param keys The keys + * @param keys The keys * @return The member with the highest score, or None if the sets are empty */ - def bzPopMax(timeout: FiniteDuration, keys: K*): Future[Option[ScoreWithKeyValue[K,V]]] + def bzPopMax(timeout: FiniteDuration, keys: K*): Future[Option[ScoreWithKeyValue[K, V]]] /** * Add one or more members to a sorted set, or update its score if it already exists - * @param key The key + * + * @param key The key * @param values The values to add * @return The number of elements added to the sorted set */ @@ -60,8 +67,9 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Add one or more members to a sorted set, or update its score if it already exists - * @param key The key - * @param args Optional arguments + * + * @param key The key + * @param args Optional arguments * @param values The values to add * @return The number of elements added to the sorted set */ @@ -69,8 +77,9 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Add one or more members to a sorted set, or update its score if it already exists - * @param key The key - * @param args Optional arguments + * + * @param key The key + * @param args Optional arguments * @param values The values to add * @return The number of elements added to the sorted set */ @@ -78,6 +87,7 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Add one or more members to a sorted set, or update its score if it already exists + * * @param key The key * @return The number of members in the sorted set */ @@ -85,8 +95,9 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Add one or more members to a sorted set, or update its score if it already exists - * @param key The key - * @param args The arguments + * + * @param key The key + * @param args The arguments * @param values The values to add * @return The number of elements added to the sorted set */ @@ -94,7 +105,8 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Add one or more members to a sorted set, or update its score if it already exists - * @param key The key + * + * @param key The key * @param values The values to add * @return The number of elements added to the sorted set */ @@ -102,6 +114,7 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Get the number of members in a sorted set + * * @param key The key * @return The number of members in the sorted set */ @@ -109,32 +122,70 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Count the members in a sorted set with scores within the given values - * @param key The key + * + * @param key The key * @param range The range of scores * @return The number of elements in the specified score range */ def zCount[T: Numeric](key: K, range: ZRange[T]): Future[Long] + /** + * Diff multiple sorted sets and return the elements + * + * @param keys The keys + * @return A list of elements + */ + def zDiff(keys: K*): Future[List[V]] + + /** + * Diff multiple sorted sets and store the result in a new key + * + * @param destination The destination key + * @param keys The keys to diff + * @return The number of elements in the resulting sorted set + */ + def zDiffStore(destination: K, keys: K*): Future[Long] + + /** + * Diff multiple sorted sets and return the elements with scores + * + * @param keys The keys + * @return A list of elements with scores + */ + def zDiffWithScores(keys: K*): Future[List[ScoreWithValue[V]]] + + /** + * Count the number of members in a sorted set between a given lexicographical range. + * + * @param key The key + * @param range The range of values + * @return The number of elements in the specified lexicographical range + */ + def zLexCount(key: K, range: ZRange[V]): Future[Long] + /** * Remove and return a member from the end of one or more sorted sets based on the pop direction (min or max) + * * @param direction The direction to pop from - * (min or max) - * @param keys The keys + * (min or max) + * @param keys The keys * @return The member removed based on the pop direction */ - def zMPop(direction: SortOrder, keys: K*): Future[Option[ScoreWithKeyValue[K,V]]] + def zMPop(direction: SortOrder, keys: K*): Future[Option[ScoreWithKeyValue[K, V]]] /** * Remove and return up to count members from the end of one or more sorted sets based on the pop direction (min or max) - * @param count The number of members to pop + * + * @param count The number of members to pop * @param direction The direction to pop from - * @param keys The keys + * @param keys The keys * @return The members removed based on the pop direction */ - def zMPop(count: Int, direction: SortOrder, keys: K*): Future[List[ScoreWithKeyValue[K,V]]] + def zMPop(count: Int, direction: SortOrder, keys: K*): Future[List[ScoreWithKeyValue[K, V]]] /** * Remove and return a member with the lowest score from a sorted set + * * @param key The key * @return The member with the lowest score, or None if the set is empty */ @@ -142,7 +193,8 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Remove and return up to count members with the lowest scores in a sorted set - * @param key The key + * + * @param key The key * @param count The number of members to pop * @return The members with the lowest scores */ @@ -150,6 +202,7 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Remove and return a member with the highest score from a sorted set + * * @param key The key * @return The member with the highest score, or None if the set is empty */ @@ -157,7 +210,8 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Remove and return up to count members with the highest scores in a sorted set - * @param key The key + * + * @param key The key * @param count The number of members to pop * @return The members with the highest scores */ @@ -165,33 +219,88 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Get the score of a member in a sorted set - * @param key The key + * + * @param key The key * @param value The value * @return The score of the member, or None if the member does not exist */ def zScore(key: K, value: V): Future[Option[Double]] + /** + * Get the scores of multiple members in a sorted set + * + * @param key The key + * @param members The members + * @return The scores of the members + */ + def zMScore(key: K, members: V*): Future[List[Option[Double]]] + /** * Return a range of members in a sorted set, by index - * @param key The key + * + * @param key The key * @param start The start index - * @param stop The stop index + * @param stop The stop index * @return The members in the specified range */ def zRange(key: K, start: Long, stop: Long): Future[List[V]] + /** + * Store a range of members in a sorted set, by index + * + * @param destination The destination key + * @param key The key to get the range from + * @param start The start index + * @param stop The stop index + * @return The number of elements in the resulting sorted set + */ + def zRangeStore(destination: K, key: K, start: Long, stop: Long): Future[Long] + + /** + * Store a range of members in a sorted set, by lexicographical range + * + * @param destination The destination key + * @param key The key to get the range from + * @param range The range of indexes + * @return The number of elements in the resulting sorted set + */ + def zRangeStoreByLex(destination: K, key: K, range: ZRange[V], limit: Option[RangeLimit] = None): Future[Long] + + /** + * Store a range of members in a sorted set, by score + * + * @param destination The destination key + * @param key The key to get the range from + * @param range The range of scores + * @param limit Optional limit + * @return The number of elements in the resulting sorted set + */ + def zRangeStoreByScore[T: Numeric](destination: K, key: K, range: ZRange[T], limit: Option[RangeLimit] = None): Future[Long] + /** * Return a range of members with scores in a sorted set, by index. - * @param key The key + * + * @param key The key * @param start The start index - * @param stop The stop index + * @param stop The stop index * @return The members with scores in the specified range */ def zRangeWithScores(key: K, start: Long, stop: Long): Future[List[ScoreWithValue[V]]] + /** + * Return a range of members in a sorted set, by lexicographical range + * + * @param key The key + * @param range The range of values + * @param limit Optional limit + * @return The members in the specified lexicographical range + */ + def zRangeByLex(key: K, range: ZRange[V], limit: Option[RangeLimit] = None): Future[List[V]] + /** * Return a range of members in a sorted set, by score - * @param key The key + * + * @param key The key * @param range The range of scores * @param limit Optional limit * @return The members in the specified score range @@ -200,16 +309,28 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Return a range of members in a sorted set, by score, with scores ordered from high to low - * @param key The key + * + * @param key The key * @param range The range of scores * @param limit Optional limit * @return The members in the specified score range */ def zRangeByScoreWithScores[T: Numeric](key: K, range: ZRange[T], limit: Option[RangeLimit] = None): Future[List[ScoreWithValue[V]]] + /** + * Return a range of members in a sorted set, by lexicographical range, from the high to low end + * + * @param key The key + * @param range The range of values + * @param limit Optional limit + * @return The members in the specified lexicographical range + */ + def zRevRangeByLex(key: K, range: ZRange[V], limit: Option[RangeLimit] = None): Future[List[V]] + /** * Return a range of members in a sorted set, by score, with scores ordered from high to low - * @param key The key + * + * @param key The key * @param range The range of scores * @param limit Optional limit * @return The members in the specified score range @@ -218,7 +339,8 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Return a range of members in a sorted set, by score, with scores ordered from high to low - * @param key The key + * + * @param key The key * @param range The range of scores * @param limit Optional limit * @return The members in the specified score range @@ -227,16 +349,80 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Increment the score of a member in a sorted set - * @param key The key + * + * @param key The key * @param amount The amount to increment by - * @param value The value + * @param value The value * @return The new score of the member */ def zIncrBy(key: K, amount: Double, value: V): Future[Double] + + /** + * Intersect multiple sorted sets + * + * @param keys The keys + * @return The matching elements + */ + def zInter(keys: K*): Future[List[V]] + + /** + * Intersect multiple sorted sets + * + * @param args Arguments to define aggregation and weights. + * @param keys The keys + * @return The matching elements + */ + def zInter(args: AggregationArgs, keys: K*): Future[List[V]] + + /** + * Intersect multiple sorted sets and return the cardinality of the resulting intersection + * + * @param keys The keys + * @return The number of elements in the resulting intersection + */ + def zInterCard(keys: K*): Future[Long] + + /** + * Intersect multiple sorted sets and store the result in a new key + * + * @param destination The destination key + * @param keys The keys + * @return The number of elements in the resulting sorted set + */ + def zInterStore(destination: K, keys: K*): Future[Long] + + /** + * Intersect multiple sorted sets and store the result in a new key + * + * @param destination The destination key + * @param args The Arguments to define aggregation and weights. + * @param keys The keys + * @return The number of elements in the resulting sorted set + */ + def zInterStore(destination: K, args: AggregationArgs, keys: K*): Future[Long] + + /** + * Intersect multiple sorted sets + * + * @param keys The keys + * @return The matching elements with scores + */ + def zInterWithScores(keys: K*): Future[List[ScoreWithValue[V]]] + + /** + * Intersect multiple sorted sets + * + * @param args Arguments to define aggregation and weights. + * @param keys The keys + * @return The matching elements with scores + */ + def zInterWithScores(args: AggregationArgs, keys: K*): Future[List[ScoreWithValue[V]]] + /** * Determine the index of a member in a sorted set - * @param key The key + * + * @param key The key * @param value The value * @return The index of the member, or None if the member does not exist */ @@ -244,7 +430,8 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Determine the index of a member in a sorted set, with the score - * @param key The key + * + * @param key The key * @param value The value * @return The index of the member, or None if the member does not exist */ @@ -252,7 +439,8 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Determine the index of a member in a sorted set, with scores ordered from high to low. - * @param key The key. + * + * @param key The key. * @param value the member type: value. * @return Long integer-reply the rank of the element as an integer-reply, with the scores ordered from high to low * or None if the member does not exist. @@ -261,7 +449,8 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Determine the index of a member in a sorted set, with the score - * @param key The key + * + * @param key The key * @param value The value * @return The index of the member, or None if the member does not exist */ @@ -269,9 +458,10 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Scan a sorted set - * @param key The key - * @param cursor The cursor - * @param limit The maximum number of elements to return + * + * @param key The key + * @param cursor The cursor + * @param limit The maximum number of elements to return * @param matchPattern The pattern to match * @return The cursor and the values */ @@ -279,6 +469,7 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Get a random member from a sorted set + * * @param key The key * @return A random member from the sorted set, or None if the set is empty */ @@ -286,7 +477,8 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Get multiple random members from a sorted set - * @param key The key + * + * @param key The key * @param count The number of members to get * @return A list of random members from the sorted set */ @@ -294,6 +486,7 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Get a random member from a sorted set, with the score + * * @param key The key * @return A random member from the sorted set, or None if the set is empty */ @@ -301,7 +494,8 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Get multiple random members from a sorted set, with the score - * @param key The key + * + * @param key The key * @param count The number of members to get * @return A list of random members from the sorted set */ @@ -309,24 +503,36 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Remove one or more members from a sorted set - * @param key The key + * + * @param key The key * @param values The values to remove * @return The number of members removed from the sorted set */ def zRem(key: K, values: V*): Future[Long] + /** + * Remove one or more members from a sorted set by lexicographical range + * + * @param key The key + * @param range The range of values by which to remove members, based on lexicographical range + * @return The number of members removed from the sorted set + */ + def zRemRangeByLex(key: K, range: ZRange[V]): Future[Long] + /** * Remove all members in a sorted set with scores between the given values - * @param key The key + * + * @param key The key * @param start The start score - * @param stop The stop score + * @param stop The stop score * @return The number of members removed from the sorted set */ def zRemRangeByRank(key: K, start: Long, stop: Long): Future[Long] /** * Remove all members in a sorted set with scores between the given values - * @param key The key + * + * @param key The key * @param range The range of scores * @return The number of members removed from the sorted set */ @@ -334,60 +540,166 @@ trait RedisSortedSetAsyncCommands[K, V] { /** * Get the score of a member in a sorted set, with the score - * @param key The key + * + * @param key The key * @param start The start index - * @param stop The stop index + * @param stop The stop index * @return The members with scores in the specified range */ def zRevRange(key: K, start: Long, stop: Long): Future[List[V]] + /** + * Store a range of members in a sorted set, by index + * + * @param destination The destination key + * @param key The key to get the range from + * @param start The start index + * @param stop The stop index + * @return The number of elements in the resulting sorted set + */ + def zRevRangeStore(destination: K, key: K, start: Long, stop: Long): Future[Long] + + /** + * Store a range of members in a sorted set, by lexicographical range + * + * @param destination The destination key + * @param key The key to get the range from + * @param range The range of indexes + * @return The number of elements in the resulting sorted set + */ + def zRevRangeStoreByLex(destination: K, key: K, range: ZRange[V], limit: Option[RangeLimit] = None): Future[Long] + + /** + * Store a range of members in a sorted set, by score + * + * @param destination The destination key + * @param key The key to get the range from + * @param range The range of scores + * @param limit Optional limit + * @return The number of elements in the resulting sorted set + */ + def zRevRangeStoreByScore[T: Numeric](destination: K, key: K, range: ZRange[T], limit: Option[RangeLimit] = None): Future[Long] + /** * Get the score of a member in a sorted set, with the score - * @param key The key + * + * @param key The key * @param start The start index - * @param stop The stop index + * @param stop The stop index * @return The members with scores in the specified range */ def zRevRangeWithScores(key: K, start: Long, stop: Long): Future[List[ScoreWithValue[V]]] -} -// TODO : Implement the following commands: -//zdiff -//zdiffstore -//zdiffWithScores -//zinter -//zintercard -//zinterWithScores -//zinterstore -//zlexcount -//zlexcount -//zmscore -//zmpop -//zrandmemberWithScores -//zrangebylex -//zrangestore -//zrangestorebylex -//zrangestorebyscore -//zremrangebylex -//zrevrangebylex -//zrevrangestore -//zrevrangestorebylex -//zrevrangestorebyscore -//zunion -//zunionWithScores -//zunionstore + /** + * Add multiple sorted sets and returns the resulting sorted set. + * + * @param keys The keys + * @return The resulting sorted set from the union + */ + def zUnion(keys: K*): Future[List[V]] + + /** + * Add multiple sorted sets and returns the resulting sorted set. + * + * @param args Arguments to define aggregation and weights. + * @param keys The keys + * @return The resulting sorted set from the union + */ + def zUnion(args: AggregationArgs, keys: K*): Future[List[V]] + + /** + * Add multiple sorted sets and returns the resulting sorted set with scores. + * + * @param keys The keys + * @return The resulting sorted set from the union + */ + def zUnionWithScores(keys: K*): Future[List[ScoreWithValue[V]]] + + /** + * Add multiple sorted sets and returns the resulting sorted set with scores. + * + * @param args Arguments to define aggregation and weights. + * @param keys The keys + * @return The resulting sorted set from the union + */ + def zUnionWithScores(args: AggregationArgs, keys: K*): Future[List[ScoreWithValue[V]]] + + /** + * Add multiple sorted sets and store the result in a new key + * + * @param destination The destination key + * @param keys The keys to union + * @return The number of elements in the resulting sorted set + */ + def zUnionStore(destination: K, keys: K*): Future[Long] + + /** + * Add multiple sorted sets and store the result in a new key + * + * @param destination The destination key + * @param args Arguments to define aggregation and weights. + * @param keys The keys to union + * @return The number of elements in the resulting sorted set + */ + def zUnionStore(destination: K, args: AggregationArgs, keys: K*): Future[Long] +} /** * Companion object for RedisSortedSetAsyncCommands */ object RedisSortedSetAsyncCommands { + /** + * A score with a value + * + * @param score The score + * @param value The value + * @tparam V The value type + */ + final case class ScoreWithValue[V](score: Double, value: V) + + /** + * A score with a key and value + * + * @param score The score + * @param key The key + * @param value The value + * @tparam K The key type + * @tparam V The value type + */ + final case class ScoreWithKeyValue[K, V](score: Double, key: K, value: V) + + /** + * A range of values + * + * @param start The start value + * @param end The end value + * @tparam T The value type + */ + final case class ZRange[T](start: T, end: T) + + /** + * A range limit + * + * @param offset The offset + * @param count The count + */ + final case class RangeLimit(offset: Long, count: Long) + + /** + * A set of options for ZADD + */ sealed trait ZAddOptions + + /** + * Companion object for ZAddOptions + */ object ZAddOptions { /** * Takes a varargs of ZAddOptions and returns a Set of ZAddOptions. * Useful for passing multiple options to a command. + * * @param options The options * @return The set of options */ @@ -419,15 +731,57 @@ object RedisSortedSetAsyncCommands { case object CH extends ZAddOptions } - final case class ScoreWithValue[V](score: Double, value: V) - final case class ScoreWithKeyValue[K, V](score: Double, key: K, value: V) - final case class ZRange[T](start: T, end: T) - final case class RangeLimit(offset: Long, count: Long) + /** + * The order in which to sort the elements + */ sealed trait SortOrder + /** + * Companion object for SortOrder + */ object SortOrder { + /** + * Sort the elements in ascending order + */ case object Min extends SortOrder + + /** + * Sort the elements in descending order + */ case object Max extends SortOrder } + + /** + * The type of aggregation to perform + */ + sealed trait Aggregate + + /** + * Companion object for Aggregate + */ + object Aggregate { + /** + * Sum the elements + */ + case object Sum extends Aggregate + + /** + * Get the minimum element + */ + case object Min extends Aggregate + + /** + * Get the maximum element + */ + case object Max extends Aggregate + } + + /** + * Arguments for aggregation + * + * @param aggregate The type of aggregation + * @param weights The weights + */ + case class AggregationArgs(aggregate: Aggregate = Aggregate.Sum, weights: Seq[Double] = Seq.empty) } \ No newline at end of file diff --git a/modules/core/src/main/scala/com/github/scoquelin/arugula/commands/LettuceRedisSortedSetAsyncCommands.scala b/modules/core/src/main/scala/com/github/scoquelin/arugula/commands/LettuceRedisSortedSetAsyncCommands.scala index adc6950..01b4253 100644 --- a/modules/core/src/main/scala/com/github/scoquelin/arugula/commands/LettuceRedisSortedSetAsyncCommands.scala +++ b/modules/core/src/main/scala/com/github/scoquelin/arugula/commands/LettuceRedisSortedSetAsyncCommands.scala @@ -96,6 +96,19 @@ private[arugula] trait LettuceRedisSortedSetAsyncCommands[K, V] extends RedisSor override def zCount[T: Numeric](key: K, range: ZRange[T]): Future[Long] = delegateRedisClusterCommandAndLift(_.zcount(key, toJavaNumberRange(range))).map(Long2long) + override def zDiff(keys: K*): Future[List[V]] = + delegateRedisClusterCommandAndLift(_.zdiff(keys: _*)).map(_.asScala.toList) + + override def zDiffStore(destination: K, keys: K*): Future[Long] = + delegateRedisClusterCommandAndLift(_.zdiffstore(destination, keys: _*)).map(Long2long) + + override def zDiffWithScores(keys: K*): Future[List[ScoreWithValue[V]]] = + delegateRedisClusterCommandAndLift(_.zdiffWithScores(keys: _*)).map(_.asScala.toList.map(scoredValue => ScoreWithValue(scoredValue.getScore, scoredValue.getValue))) + + override def zLexCount(key: K, range: ZRange[V]): Future[Long] = { + delegateRedisClusterCommandAndLift(_.zlexcount(key, Range.create(range.start, range.end))).map(Long2long) + } + override def zMPop(direction: SortOrder, keys: K*): Future[Option[ScoreWithKeyValue[K, V]]] = { val args = direction match { case SortOrder.Min => io.lettuce.core.ZPopArgs.Builder.min() @@ -157,9 +170,33 @@ private[arugula] trait LettuceRedisSortedSetAsyncCommands[K, V] extends RedisSor override def zScore(key: K, value: V): Future[Option[Double]] = delegateRedisClusterCommandAndLift(_.zscore(key, value)).map(Option(_).map(Double2double)) + override def zMScore(key: K, members: V*): Future[List[Option[Double]]] = { + delegateRedisClusterCommandAndLift(_.zmscore(key, members: _*)).map(_.asScala.map(Option(_).map(Double2double)).toList) + } + override def zRange(key: K, start: Long, stop: Long): Future[List[V]] = delegateRedisClusterCommandAndLift(_.zrange(key, start, stop)).map(_.asScala.toList) + override def zRangeStore(destination: K, key: K, start: Long, stop: Long): Future[Long] = { + delegateRedisClusterCommandAndLift(_.zrangestore(destination, key, Range.create(start, stop))).map(Long2long) + } + + override def zRangeStoreByLex(destination: K, key: K, range: ZRange[V], limit: Option[RangeLimit] = None): Future[Long] = { + val args = limit match { + case Some(rangeLimit) => io.lettuce.core.Limit.create(rangeLimit.offset, rangeLimit.count) + case None => io.lettuce.core.Limit.unlimited() + } + delegateRedisClusterCommandAndLift(_.zrangestorebylex(destination, key, Range.create(range.start, range.end), args)).map(Long2long) + } + + override def zRangeStoreByScore[T: Numeric](destination: K, key: K, range: ZRange[T], limit: Option[RangeLimit] = None): Future[Long] = { + val limitArgs = limit match { + case Some(rangeLimit) => Limit.create(rangeLimit.offset, rangeLimit.count) + case None => Limit.unlimited() + } + delegateRedisClusterCommandAndLift(_.zrangestorebyscore(destination, key, toJavaNumberRange(range), limitArgs)).map(Long2long) + } + override def zRangeByScore[T: Numeric](key: K, range: ZRange[T], limit: Option[RangeLimit] = None): Future[List[V]] = limit match { case Some(rangeLimit) => @@ -178,6 +215,16 @@ private[arugula] trait LettuceRedisSortedSetAsyncCommands[K, V] extends RedisSor .map(_.asScala.toList.map(scoredValue => ScoreWithValue(scoredValue.getScore, scoredValue.getValue))) } + override def zRevRangeByLex(key: K, + range: ZRange[V], + limit: Option[RangeLimit] = None): Future[List[V]] = { + val args = limit match { + case Some(rangeLimit) => io.lettuce.core.Limit.create(rangeLimit.offset, rangeLimit.count) + case None => io.lettuce.core.Limit.unlimited() + } + delegateRedisClusterCommandAndLift(_.zrevrangebylex(key, Range.create(range.start, range.end), args)).map(_.asScala.toList) + } + override def zRevRangeByScore[T: Numeric](key: K, range: ZRange[T], limit: Option[RangeLimit] = None): Future[List[V]] = limit match { case Some(rangeLimit) => @@ -203,6 +250,31 @@ private[arugula] trait LettuceRedisSortedSetAsyncCommands[K, V] extends RedisSor override def zIncrBy(key: K, amount: Double, value: V): Future[Double] = delegateRedisClusterCommandAndLift(_.zincrby(key, amount, value)).map(Double2double) + override def zInter(keys: K*): Future[List[V]] = + delegateRedisClusterCommandAndLift(_.zinter(keys: _*)).map(_.asScala.toList) + + override def zInter(args: RedisSortedSetAsyncCommands.AggregationArgs, keys: K*): Future[List[V]] = { + delegateRedisClusterCommandAndLift(_.zinter(LettuceRedisSortedSetAsyncCommands.aggregationArgsToJava(args), keys: _*)).map(_.asScala.toList) + } + + override def zInterCard(keys: K*): Future[Long] = + delegateRedisClusterCommandAndLift(_.zintercard(keys: _*)).map(Long2long) + + override def zInterStore(destination: K, keys: K*): Future[Long] = + delegateRedisClusterCommandAndLift(_.zinterstore(destination, keys: _*)).map(Long2long) + + override def zInterStore(destination: K, args: RedisSortedSetAsyncCommands.AggregationArgs, keys: K*): Future[Long] = { + delegateRedisClusterCommandAndLift(_.zinterstore(destination, LettuceRedisSortedSetAsyncCommands.aggregationArgsToJavaStore(args), keys: _*)).map(Long2long) + } + + override def zInterWithScores(keys: K*): Future[List[ScoreWithValue[V]]] = + delegateRedisClusterCommandAndLift(_.zinterWithScores(keys: _*)).map(_.asScala.toList.map(scoredValue => ScoreWithValue(scoredValue.getScore, scoredValue.getValue))) + + override def zInterWithScores(args: RedisSortedSetAsyncCommands.AggregationArgs, keys: K*): Future[List[ScoreWithValue[V]]] = { + delegateRedisClusterCommandAndLift(_.zinterWithScores(LettuceRedisSortedSetAsyncCommands.aggregationArgsToJava(args), keys: _*)) + .map(_.asScala.toList.map(scoredValue => ScoreWithValue(scoredValue.getScore, scoredValue.getValue))) + } + override def zRank(key: K, value: V): Future[Option[Long]] = delegateRedisClusterCommandAndLift(_.zrank(key, value)).map(Option(_).map(Long2long)) @@ -219,6 +291,14 @@ private[arugula] trait LettuceRedisSortedSetAsyncCommands[K, V] extends RedisSor delegateRedisClusterCommandAndLift(_.zrangeWithScores(key, start, stop)) .map(_.asScala.toList.map(scoredValue => ScoreWithValue(scoredValue.getScore, scoredValue.getValue))) + override def zRangeByLex(key: K, range: ZRange[V], limit: Option[RangeLimit] = None): Future[List[V]] = { + val args = limit match { + case Some(rangeLimit) => io.lettuce.core.Limit.create(rangeLimit.offset, rangeLimit.count) + case None => io.lettuce.core.Limit.unlimited() + } + delegateRedisClusterCommandAndLift(_.zrangebylex(key, Range.create(range.start, range.end), args)).map(_.asScala.toList) + } + override def zScan(key: K, cursor: String = InitialCursor, limit: Option[Long] = None, matchPattern: Option[String] = None): Future[ScanResults[List[ScoreWithValue[V]]]] = { val scanArgs = (limit, matchPattern) match { case (Some(limitValue), Some(matchPatternValue)) => @@ -266,6 +346,9 @@ private[arugula] trait LettuceRedisSortedSetAsyncCommands[K, V] extends RedisSor override def zRem(key: K, values: V*): Future[Long] = delegateRedisClusterCommandAndLift(_.zrem(key, values: _*)).map(Long2long) + override def zRemRangeByLex(key: K, range: ZRange[V]): Future[Long] = + delegateRedisClusterCommandAndLift(_.zremrangebylex(key, Range.create(range.start, range.end))).map(Long2long) + override def zRemRangeByRank(key: K, start: Long, stop: Long): Future[Long] = delegateRedisClusterCommandAndLift(_.zremrangebyrank(key, start, stop)).map(Long2long) @@ -275,10 +358,55 @@ private[arugula] trait LettuceRedisSortedSetAsyncCommands[K, V] extends RedisSor override def zRevRange(key: K, start: Long, stop: Long): Future[List[V]] = delegateRedisClusterCommandAndLift(_.zrevrange(key, start, stop)).map(_.asScala.toList) + override def zRevRangeStore(destination: K, key: K, start: Long, stop: Long): Future[Long] = + delegateRedisClusterCommandAndLift(_.zrevrangestore(destination, key, Range.create(start, stop))).map(Long2long) + + override def zRevRangeStoreByLex(destination: K, key: K, range: ZRange[V], limit: Option[RangeLimit]): Future[Long] = { + val args = limit match { + case Some(rangeLimit) => io.lettuce.core.Limit.create(rangeLimit.offset, rangeLimit.count) + case None => io.lettuce.core.Limit.unlimited() + } + delegateRedisClusterCommandAndLift(_.zrevrangestorebylex(destination, key, Range.create(range.start, range.end), args)).map(Long2long) + } + + override def zRevRangeStoreByScore[T: Numeric](destination: K, + key: K, + range: ZRange[T], + limit: Option[RangeLimit]): Future[Long] = { + val limitArgs = limit match { + case Some(rangeLimit) => Limit.create(rangeLimit.offset, rangeLimit.count) + case None => Limit.unlimited() + } + delegateRedisClusterCommandAndLift(_.zrevrangestorebyscore(destination, key, toJavaNumberRange(range), limitArgs)).map(Long2long) + } + override def zRevRangeWithScores(key: K, start: Long, stop: Long): Future[List[ScoreWithValue[V]]] = delegateRedisClusterCommandAndLift(_.zrevrangeWithScores(key, start, stop)) .map(_.asScala.toList.map(scoredValue => ScoreWithValue(scoredValue.getScore, scoredValue.getValue))) + override def zUnion(keys: K*): Future[List[V]] = + delegateRedisClusterCommandAndLift(_.zunion(keys: _*)).map(_.asScala.toList) + + override def zUnion(args: RedisSortedSetAsyncCommands.AggregationArgs, keys: K*): Future[List[V]] = { + delegateRedisClusterCommandAndLift(_.zunion(LettuceRedisSortedSetAsyncCommands.aggregationArgsToJava(args), keys: _*)).map(_.asScala.toList) + } + + override def zUnionWithScores(keys: K*): Future[List[ScoreWithValue[V]]] = + delegateRedisClusterCommandAndLift(_.zunionWithScores(keys: _*)) + .map(_.asScala.toList.map(scoredValue => ScoreWithValue(scoredValue.getScore, scoredValue.getValue))) + + override def zUnionWithScores(args: RedisSortedSetAsyncCommands.AggregationArgs, keys: K*): Future[List[ScoreWithValue[V]]] = { + delegateRedisClusterCommandAndLift(_.zunionWithScores(LettuceRedisSortedSetAsyncCommands.aggregationArgsToJava(args), keys: _*)) + .map(_.asScala.toList.map(scoredValue => ScoreWithValue(scoredValue.getScore, scoredValue.getValue))) + } + + override def zUnionStore(destination: K, keys: K*): Future[Long] = + delegateRedisClusterCommandAndLift(_.zunionstore(destination, keys: _*)).map(Long2long) + + override def zUnionStore(destination: K, args: RedisSortedSetAsyncCommands.AggregationArgs, keys: K*): Future[Long] = { + delegateRedisClusterCommandAndLift(_.zunionstore(destination, LettuceRedisSortedSetAsyncCommands.aggregationArgsToJavaStore(args), keys: _*)).map(Long2long) + } + } private[this] object LettuceRedisSortedSetAsyncCommands{ @@ -306,4 +434,30 @@ private[this] object LettuceRedisSortedSetAsyncCommands{ } args } + + private[commands] def aggregationArgsToJavaStore(options: RedisSortedSetAsyncCommands.AggregationArgs): io.lettuce.core.ZStoreArgs = { + val args = options.aggregate match { + case RedisSortedSetAsyncCommands.Aggregate.Sum => io.lettuce.core.ZStoreArgs.Builder.sum() + case RedisSortedSetAsyncCommands.Aggregate.Min => io.lettuce.core.ZStoreArgs.Builder.min() + case RedisSortedSetAsyncCommands.Aggregate.Max => io.lettuce.core.ZStoreArgs.Builder.max() + } + // add weights to args. the args is a mutable java object so we can just add the weights to it + if(options.weights.nonEmpty){ + args.weights(options.weights.map(_.doubleValue()): _*) + } + args + } + + private[commands] def aggregationArgsToJava(options: RedisSortedSetAsyncCommands.AggregationArgs): io.lettuce.core.ZAggregateArgs = { + val args = options.aggregate match { + case RedisSortedSetAsyncCommands.Aggregate.Sum => io.lettuce.core.ZAggregateArgs.Builder.sum() + case RedisSortedSetAsyncCommands.Aggregate.Min => io.lettuce.core.ZAggregateArgs.Builder.min() + case RedisSortedSetAsyncCommands.Aggregate.Max => io.lettuce.core.ZAggregateArgs.Builder.max() + } + // add weights to args. the args is a mutable java object so we can just add the weights to it + if(options.weights.nonEmpty){ + args.weights(options.weights.map(_.doubleValue()): _*) + } + args + } } \ No newline at end of file diff --git a/modules/tests/it/src/test/scala/com/github/scoquelin/arugula/RedisCommandsIntegrationSpec.scala b/modules/tests/it/src/test/scala/com/github/scoquelin/arugula/RedisCommandsIntegrationSpec.scala index 79d0b1a..dd2a48c 100644 --- a/modules/tests/it/src/test/scala/com/github/scoquelin/arugula/RedisCommandsIntegrationSpec.scala +++ b/modules/tests/it/src/test/scala/com/github/scoquelin/arugula/RedisCommandsIntegrationSpec.scala @@ -4,7 +4,7 @@ import scala.collection.immutable.ListMap import scala.concurrent.Future import com.github.scoquelin.arugula.codec.RedisCodec -import com.github.scoquelin.arugula.commands.RedisSortedSetAsyncCommands.{RangeLimit, ScoreWithKeyValue, ScoreWithValue, SortOrder, ZAddOptions, ZRange} +import com.github.scoquelin.arugula.commands.RedisSortedSetAsyncCommands.{Aggregate, AggregationArgs, RangeLimit, ScoreWithKeyValue, ScoreWithValue, SortOrder, ZAddOptions, ZRange} import org.scalatest.matchers.should.Matchers import scala.concurrent.duration._ @@ -492,6 +492,10 @@ class RedisCommandsIntegrationSpec extends BaseRedisCommandsIntegrationSpec with val key = randomKey("sorted-set-incr") for { _ <- client.zAdd(key, ScoreWithValue(1, "one"), ScoreWithValue(2, "two"), ScoreWithValue(3, "three")) + zCard <- client.zCard(key) + _ <- zCard shouldBe 3L + zCount <- client.zCount(key, ZRange(0, 2)) + _ <- zCount shouldBe 2L incrResult <- client.zIncrBy(key, 2, "two") _ <- incrResult shouldBe 4.0 incrResult <- client.zIncrBy(key, 2, "four") @@ -507,6 +511,8 @@ class RedisCommandsIntegrationSpec extends BaseRedisCommandsIntegrationSpec with zRevRankWithScore <- client.zRevRankWithScore(key, "four") _ <- zRevRankWithScore shouldBe Some(ScoreWithValue(2.0, 2)) zAddIncr <- client.zAddIncr(key, ZAddOptions(ZAddOptions.XX), 2.9, "four") + zmScore <- client.zMScore(key, "four", "two") + _ <- zmScore shouldBe List(Some(4.9), Some(4.0)) _ <- zAddIncr shouldBe Some(4.9) } yield succeed } @@ -518,10 +524,13 @@ class RedisCommandsIntegrationSpec extends BaseRedisCommandsIntegrationSpec with val key1 = randomKey("sorted-set1") + suffix val key2 = randomKey("sorted-set2") + suffix val key3 = randomKey("sorted-set3") + suffix + val destination = randomKey("sorted-set-destination") + suffix for { _ <- client.zAdd(key1, ScoreWithValue(1, "one"), ScoreWithValue(2, "two"), ScoreWithValue(3, "three")) _ <- client.zAdd(key2, ScoreWithValue(4, "four"), ScoreWithValue(5, "five"), ScoreWithValue(6, "six")) _ <- client.zAdd(key3, ScoreWithValue(7, "seven"), ScoreWithValue(8, "eight"), ScoreWithValue(9, "nine")) + zrangeStore <- client.zRangeStore(destination, key1, 0, -1) + _ <- zrangeStore shouldBe 3L bzPopMin <- client.bzPopMin(100.milliseconds, key1, key2, key3) _ <- bzPopMin shouldBe Some(ScoreWithKeyValue(1, key1, "one")) bzPopMax <- client.bzPopMax(100.milliseconds, key3, key2, key1) @@ -534,6 +543,65 @@ class RedisCommandsIntegrationSpec extends BaseRedisCommandsIntegrationSpec with _ <- zMPop shouldBe Some(ScoreWithKeyValue(4.0, key2, "four")) zMPopWithCount <- client.zMPop(2, SortOrder.Max, key3, key2) _ <- zMPopWithCount shouldBe List(ScoreWithKeyValue(6.0, key2, "six"), ScoreWithKeyValue(5.0, key2, "five")) + + + } yield succeed + } + } + + "support lexographical range operations" in { + withRedisSingleNodeAndCluster(RedisCodec.Utf8WithValueAsStringCodec) { client => + val suffix = "{user1}" + val key = randomKey("sorted-set-lex") + suffix + val destination = randomKey("sorted-set-destination") + suffix + for { + _ <- client.zAdd(key, ScoreWithValue(1, "a"), ScoreWithValue(2, "b"), ScoreWithValue(3, "c"), ScoreWithValue(4, "d"), ScoreWithValue(5, "e")) + lexRange <- client.zRangeByLex(key, ZRange("a", "c")) + _ <- lexRange shouldBe List("a", "b", "c") + lexRange <- client.zRangeByLex(key, ZRange("a", "c"), Some(RangeLimit(0, 2))) + _ <- lexRange shouldBe List("a", "b") + revLexRange <- client.zRevRangeByLex(key, ZRange("a", "c")) + _ <- revLexRange shouldBe List("c", "b", "a") + _ <- client.zRangeStoreByLex(destination, key, ZRange("a", "c")) + destinationRange <- client.zRange(destination, 0, -1) + _ <- destinationRange shouldBe List("a", "b", "c") + } yield succeed + } + } + + "support diff, inter and union operations" in { + withRedisSingleNodeAndCluster(RedisCodec.Utf8WithValueAsStringCodec) { client => + val suffix = "{user1}" + val key1 = randomKey("sorted-set1") + suffix + val key2 = randomKey("sorted-set2") + suffix + val key3 = randomKey("sorted-set3") + suffix + val destination = randomKey("sorted-set-destination") + suffix + for { + _ <- client.zAdd(key1, ScoreWithValue(1, "a"), ScoreWithValue(2, "b"), ScoreWithValue(3, "c"), ScoreWithValue(4, "d"), ScoreWithValue(5, "e")) + _ <- client.zAdd(key2, ScoreWithValue(1, "a"), ScoreWithValue(2, "b"), ScoreWithValue(3, "c"), ScoreWithValue(4, "d"), ScoreWithValue(5, "e")) + _ <- client.zAdd(key3, ScoreWithValue(1, "a"), ScoreWithValue(2, "b"), ScoreWithValue(3, "c"), ScoreWithValue(4, "d"), ScoreWithValue(5, "e")) + zInterStore <- client.zInterStore(destination, key1, key2, key3) + _ <- zInterStore shouldBe 5L + zInterCard <- client.zInterCard(key1, key2, key3) + _ <- zInterCard shouldBe 5L + zInterRange <- client.zRange(destination, 0, -1) + _ <- zInterRange shouldBe List("a", "b", "c", "d", "e") + zUnion <- client.zUnion(key1, key2) + _ <- zUnion shouldBe List("a", "b", "c", "d", "e") + zUnionWithScores <- client.zUnionWithScores(key1, key2) + _ <- zUnionWithScores shouldBe List(ScoreWithValue(2.0, "a"), ScoreWithValue(4.0, "b"), ScoreWithValue(6.0, "c"), ScoreWithValue(8.0, "d"), ScoreWithValue(10.0, "e")) + zUnionWithScoresAndWeights <- client.zUnionWithScores(AggregationArgs(weights = Seq(2.0, 3.0)), key1, key2) + _ <- zUnionWithScoresAndWeights shouldBe List(ScoreWithValue(5.0, "a"), ScoreWithValue(10.0, "b"), ScoreWithValue(15.0, "c"), ScoreWithValue(20.0, "d"), ScoreWithValue(25.0, "e")) + zUnionWithScoresMin <- client.zUnionWithScores(AggregationArgs(Aggregate.Min), key1, key2) + _ <- zUnionWithScoresMin shouldBe List(ScoreWithValue(1.0, "a"), ScoreWithValue(2.0, "b"), ScoreWithValue(3.0, "c"), ScoreWithValue(4.0, "d"), ScoreWithValue(5.0, "e")) + zUnionStore <- client.zUnionStore(destination, key1, key2, key3) + _ <- zUnionStore shouldBe 5L + zUnionRange <- client.zRange(destination, 0, -1) + _ <- zUnionRange shouldBe List("a", "b", "c", "d", "e") + zDiffStore <- client.zDiffStore(destination, key1, key2, key3) + _ <- zDiffStore shouldBe 0L + zDiffRange <- client.zRange(destination, 0, -1) + _ <- zDiffRange shouldBe List() } yield succeed } } diff --git a/modules/tests/test/src/test/scala/com/github/scoquelin/arugula/LettuceRedisSortedSetAsyncCommandsSpec.scala b/modules/tests/test/src/test/scala/com/github/scoquelin/arugula/LettuceRedisSortedSetAsyncCommandsSpec.scala index 9c20294..2eff226 100644 --- a/modules/tests/test/src/test/scala/com/github/scoquelin/arugula/LettuceRedisSortedSetAsyncCommandsSpec.scala +++ b/modules/tests/test/src/test/scala/com/github/scoquelin/arugula/LettuceRedisSortedSetAsyncCommandsSpec.scala @@ -4,7 +4,7 @@ package com.github.scoquelin.arugula import scala.concurrent.duration.DurationInt import com.github.scoquelin.arugula.commands.RedisSortedSetAsyncCommands -import com.github.scoquelin.arugula.commands.RedisSortedSetAsyncCommands.{RangeLimit, ScoreWithValue, SortOrder, ZAddOptions, ZRange} +import com.github.scoquelin.arugula.commands.RedisSortedSetAsyncCommands.{Aggregate, RangeLimit, ScoreWithValue, SortOrder, ZAddOptions, AggregationArgs, ZRange} import io.lettuce.core.{KeyValue, RedisFuture, ScoredValue, ScoredValueScanCursor} import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.{any, eq => meq} @@ -172,6 +172,177 @@ class LettuceRedisSortedSetAsyncCommandsSpec extends wordspec.FixtureAsyncWordSp } } + "delegate ZDIFF command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue: java.util.List[String] = new java.util.ArrayList[String] + expectedValue.add(0, "one") + val mockRedisFuture: RedisFuture[java.util.List[String]] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zdiff("key1", "key2")).thenReturn(mockRedisFuture) + + testClass.zDiff("key1", "key2").map { result => + result mustBe List("one") + verify(lettuceAsyncCommands).zdiff("key1", "key2") + succeed + } + } + + "delegate ZDIFFSTORE command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue = 1L + val mockRedisFuture: RedisFuture[java.lang.Long] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zdiffstore("destination", "key1", "key2")).thenReturn(mockRedisFuture) + + testClass.zDiffStore("destination", "key1", "key2").map { result => + result mustBe expectedValue + verify(lettuceAsyncCommands).zdiffstore("destination", "key1", "key2") + succeed + } + } + + "delegate ZDIFFWITHSCORES command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue: java.util.List[ScoredValue[String]] = new java.util.ArrayList[ScoredValue[String]] + expectedValue.add(ScoredValue.just(1, "one")) + val mockRedisFuture: RedisFuture[java.util.List[ScoredValue[String]]] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zdiffWithScores("key1", "key2")).thenReturn(mockRedisFuture) + + testClass.zDiffWithScores("key1", "key2").map { result => + result mustBe List(ScoreWithValue(1, "one")) + verify(lettuceAsyncCommands).zdiffWithScores("key1", "key2") + succeed + } + } + + "delegate ZINTER command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue: java.util.List[String] = new java.util.ArrayList[String] + expectedValue.add(0, "one") + val mockRedisFuture: RedisFuture[java.util.List[String]] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zinter("key1", "key2")).thenReturn(mockRedisFuture) + + testClass.zInter("key1", "key2").map { result => + result mustBe List("one") + verify(lettuceAsyncCommands).zinter("key1", "key2") + succeed + } + } + + "delegate ZINTER command with weights to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue: java.util.List[String] = new java.util.ArrayList[String] + expectedValue.add(0, "one") + val mockRedisFuture: RedisFuture[java.util.List[String]] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zinter(any[io.lettuce.core.ZStoreArgs], any[String], any[String])).thenReturn(mockRedisFuture) + + testClass.zInter(AggregationArgs(weights = Seq(1.0, 2.0)), "key1", "key2").map { result => + result mustBe List("one") + verify(lettuceAsyncCommands).zinter(any[io.lettuce.core.ZAggregateArgs], meq("key1"), meq("key2")) + succeed + } + } + + "delegate ZINTER command with weights and AGGREGATE MIN to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue: java.util.List[String] = new java.util.ArrayList[String] + expectedValue.add(0, "one") + val mockRedisFuture: RedisFuture[java.util.List[String]] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zinter(any[io.lettuce.core.ZStoreArgs], any[String], any[String])).thenReturn(mockRedisFuture) + + testClass.zInter(AggregationArgs(Aggregate.Min, weights = Seq(1.0, 2.0)), "key1", "key2").map { result => + result mustBe List("one") + verify(lettuceAsyncCommands).zinter(any[io.lettuce.core.ZAggregateArgs], meq("key1"), meq("key2")) + succeed + } + } + + "delegate ZINTERCARD command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue = 1L + val mockRedisFuture: RedisFuture[java.lang.Long] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zintercard("key1", "key2")).thenReturn(mockRedisFuture) + + testClass.zInterCard("key1", "key2").map { result => + result mustBe expectedValue + verify(lettuceAsyncCommands).zintercard("key1", "key2") + succeed + } + } + + "delegate ZINTERSTORE command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue = 1L + val mockRedisFuture: RedisFuture[java.lang.Long] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zinterstore("destination", "key1", "key2")).thenReturn(mockRedisFuture) + + testClass.zInterStore("destination", "key1", "key2").map { result => + result mustBe expectedValue + verify(lettuceAsyncCommands).zinterstore("destination", "key1", "key2") + succeed + } + } + + "delegate ZINTERSTORE command with weights to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue = 1L + val mockRedisFuture: RedisFuture[java.lang.Long] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zinterstore(any[String], any[io.lettuce.core.ZStoreArgs], any[String], any[String])).thenReturn(mockRedisFuture) + + testClass.zInterStore("destination", AggregationArgs(weights = Seq(1.0, 2.0)), "key1", "key2").map { result => + result mustBe expectedValue + verify(lettuceAsyncCommands).zinterstore(meq("destination"), any[io.lettuce.core.ZStoreArgs], meq("key1"), meq("key2")) + succeed + } + } + + "delegate ZINTERWITHSCORES command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue: java.util.List[ScoredValue[String]] = new java.util.ArrayList[ScoredValue[String]] + expectedValue.add(ScoredValue.just(1, "one")) + val mockRedisFuture: RedisFuture[java.util.List[ScoredValue[String]]] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zinterWithScores("key1", "key2")).thenReturn(mockRedisFuture) + + testClass.zInterWithScores("key1", "key2").map { result => + result mustBe List(ScoreWithValue(1, "one")) + verify(lettuceAsyncCommands).zinterWithScores("key1", "key2") + succeed + } + } + + "delegate ZLEXCOUNT command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue = 1L + val mockRedisFuture: RedisFuture[java.lang.Long] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zlexcount("key", io.lettuce.core.Range.create("b", "f"))).thenReturn(mockRedisFuture) + + testClass.zLexCount("key", ZRange("b", "f")).map { result => + result mustBe expectedValue + verify(lettuceAsyncCommands).zlexcount("key", io.lettuce.core.Range.create("b", "f")) + succeed + } + } + "delegate ZMPOP command to Lettuce and lift result into a Future" in { testContext => import testContext._ @@ -235,6 +406,22 @@ class LettuceRedisSortedSetAsyncCommandsSpec extends wordspec.FixtureAsyncWordSp } } + "delegate ZRANGEBYLEX command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue: java.util.List[String] = new java.util.ArrayList[String] + expectedValue.add(0, "one") + val mockRedisFuture: RedisFuture[java.util.List[String]] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zrangebylex("key", io.lettuce.core.Range.create("b", "f"), io.lettuce.core.Limit.unlimited())).thenReturn(mockRedisFuture) + + testClass.zRangeByLex("key", ZRange("b", "f"), None).map { result => + result mustBe List("one") + verify(lettuceAsyncCommands).zrangebylex("key", io.lettuce.core.Range.create("b", "f"), io.lettuce.core.Limit.unlimited()) + succeed + } + } + "delegate ZRANGEBYSCORE command to Lettuce and lift result into a Future" in { testContext => import testContext._ @@ -267,6 +454,51 @@ class LettuceRedisSortedSetAsyncCommandsSpec extends wordspec.FixtureAsyncWordSp } } + "delegate ZRANGESTORE command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue = 1L + val mockRedisFuture: RedisFuture[java.lang.Long] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zrangestore(meq("destination"), meq("key"), any[io.lettuce.core.Range[java.lang.Long]])).thenReturn(mockRedisFuture) + + testClass.zRangeStore("destination", "key", 0, 1).map { result => + result mustBe expectedValue + verify(lettuceAsyncCommands).zrangestore("destination", "key", io.lettuce.core.Range.create(0L, 1L)) + succeed + } + } + + "delegate ZRANGESTOREBYLEX command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue = 1L + val mockRedisFuture: RedisFuture[java.lang.Long] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zrangestorebylex(meq("destination"), meq("key"), any[io.lettuce.core.Range[String]], any[io.lettuce.core.Limit])).thenReturn(mockRedisFuture) + + testClass.zRangeStoreByLex("destination", "key", ZRange("b", "f")).map { result => + result mustBe expectedValue + verify(lettuceAsyncCommands).zrangestorebylex("destination", "key", io.lettuce.core.Range.create("b", "f"), io.lettuce.core.Limit.unlimited()) + succeed + } + } + + "delegate ZRANGESTOREBYSCORE command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue = 1L + val mockRedisFuture: RedisFuture[java.lang.Long] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zrangestorebyscore(meq("destination"), meq("key"), any[io.lettuce.core.Range[java.lang.Number]], any[io.lettuce.core.Limit])).thenReturn(mockRedisFuture) + + testClass.zRangeStoreByScore("destination", "key", ZRange(Double.NegativeInfinity, Double.PositiveInfinity)).map { result => + result mustBe expectedValue + verify(lettuceAsyncCommands).zrangestorebyscore("destination", "key", io.lettuce.core.Range.create(Double.NegativeInfinity, Double.PositiveInfinity), io.lettuce.core.Limit.unlimited()) + succeed + } + } + "delegate ZRANGE WITHSCORES command to Lettuce and lift result into a Future" in { testContext => import testContext._ @@ -392,6 +624,22 @@ class LettuceRedisSortedSetAsyncCommandsSpec extends wordspec.FixtureAsyncWordSp } } + "delegate ZMSCORE command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue = new java.util.ArrayList[java.lang.Double] + expectedValue.add(0, 1.0) + val mockRedisFuture: RedisFuture[java.util.List[java.lang.Double]] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zmscore("key", "one")).thenReturn(mockRedisFuture) + + testClass.zMScore("key", "one").map { result => + result mustBe List(Some(1.0)) + verify(lettuceAsyncCommands).zmscore("key", "one") + succeed + } + } + "delegate ZREVRANGE command to Lettuce and lift result into a Future" in { testContext => import testContext._ @@ -408,6 +656,67 @@ class LettuceRedisSortedSetAsyncCommandsSpec extends wordspec.FixtureAsyncWordSp } } + "delegate ZREVRANGESTORE command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue = 1L + val mockRedisFuture: RedisFuture[java.lang.Long] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zrevrangestore(meq("destination"), meq("key"), any[io.lettuce.core.Range[java.lang.Long]]())).thenReturn(mockRedisFuture) + + testClass.zRevRangeStore("destination", "key", 0, 1).map { result => + result mustBe expectedValue + verify(lettuceAsyncCommands).zrevrangestore("destination", "key", io.lettuce.core.Range.create(0L, 1L)) + succeed + } + } + + "delegate ZREVRANGESTOREBYLEX command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue = 1L + val mockRedisFuture: RedisFuture[java.lang.Long] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zrevrangestorebylex(meq("destination"), meq("key"), any[io.lettuce.core.Range[String]], any[io.lettuce.core.Limit])).thenReturn(mockRedisFuture) + + testClass.zRevRangeStoreByLex("destination", "key", ZRange("b", "f")).map { result => + result mustBe expectedValue + verify(lettuceAsyncCommands).zrevrangestorebylex("destination", "key", io.lettuce.core.Range.create("b", "f"), io.lettuce.core.Limit.unlimited()) + succeed + } + } + + "delegate ZREVRANGESTOREBYSCORE command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue = 1L + val mockRedisFuture: RedisFuture[java.lang.Long] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zrevrangestorebyscore(meq("destination"), meq("key"), any[io.lettuce.core.Range[java.lang.Number]], any[io.lettuce.core.Limit])).thenReturn(mockRedisFuture) + + testClass.zRevRangeStoreByScore("destination", "key", ZRange(Double.NegativeInfinity, Double.PositiveInfinity)).map { result => + result mustBe expectedValue + verify(lettuceAsyncCommands).zrevrangestorebyscore("destination", "key", io.lettuce.core.Range.create(Double.NegativeInfinity, Double.PositiveInfinity), io.lettuce.core.Limit.unlimited()) + succeed + } + } + + "delegate ZREVRANGEBYLEX command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue: java.util.List[String] = new java.util.ArrayList[String] + expectedValue.add(0, "one") + val mockRedisFuture: RedisFuture[java.util.List[String]] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zrevrangebylex("key", io.lettuce.core.Range.create("b", "f"), io.lettuce.core.Limit.unlimited())).thenReturn(mockRedisFuture) + + testClass.zRevRangeByLex("key", ZRange("b", "f"), None).map { result => + result mustBe List("one") + verify(lettuceAsyncCommands).zrevrangebylex("key", io.lettuce.core.Range.create("b", "f"), io.lettuce.core.Limit.unlimited()) + succeed + } + } + "delegate ZREVRANGE WITHSCORES command to Lettuce and lift result into a Future" in { testContext => import testContext._ @@ -536,6 +845,21 @@ class LettuceRedisSortedSetAsyncCommandsSpec extends wordspec.FixtureAsyncWordSp } } + "delegate ZREMRANGEBYLEX command to Lettuce and lift result into a Future" in { testContext => + import testContext._ + + val expectedValue = 1L + val mockRedisFuture: RedisFuture[java.lang.Long] = mockRedisFutureToReturn(expectedValue) + + when(lettuceAsyncCommands.zremrangebylex("key", io.lettuce.core.Range.create("b", "f"))).thenReturn(mockRedisFuture) + + testClass.zRemRangeByLex("key", ZRange("b", "f")).map { result => + result mustBe expectedValue + verify(lettuceAsyncCommands).zremrangebylex("key", io.lettuce.core.Range.create("b", "f")) + succeed + } + } + "delegate ZREMRANGEBYRANK command to Lettuce and lift result into a Future" in { testContext => import testContext._