Skip to content

Commit

Permalink
Merge pull request #50 from scoquelin/jl-hll
Browse files Browse the repository at this point in the history
support hyperloglog commands
  • Loading branch information
72squared authored Sep 5, 2024
2 parents b4fe04d + 8f608bd commit c4eb2ed
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ trait RedisCommandsClient[K, V]
with RedisKeyAsyncCommands[K, V]
with RedisStringAsyncCommands[K, V]
with RedisHashAsyncCommands[K, V]
with RedisHLLAsyncCommands[K, V]
with RedisListAsyncCommands[K, V]
with RedisSetAsyncCommands[K, V]
with RedisSortedSetAsyncCommands[K, V]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.github.scoquelin.arugula.commands

import scala.concurrent.Future

/**
* This trait provides Redis HyperLogLog commands.
* HyperLogLog is a probabilistic data structure used to estimate the cardinality of a set.
* @see https://redis.io/docs/latest/develop/data-types/probabilistic/hyperloglogs/
* @tparam K The key type (usually String)
* @tparam V The value type (usually String)
*/
trait RedisHLLAsyncCommands[K, V] {
/**
* Adds the specified elements to the specified HyperLogLog
* @param key The key of the HyperLogLog
* @param values The values to add
* @return The number of elements that were added to the HyperLogLog
*/
def pfAdd(key: K, values: V*): Future[Long]

/**
* Merge multiple HyperLogLogs into a single one
* @param destinationKey The key of the HyperLogLog to merge into
* @param sourceKeys The keys of the HyperLogLogs to merge
*/
def pfMerge(destinationKey: K, sourceKeys: K*): Future[Unit]

/**
* Get the number of elements in the HyperLogLog
* @param keys The keys of the HyperLogLogs to count
* @return The number of elements in the HyperLogLog
*/
def pfCount(keys: K*): Future[Long]
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ private[arugula] class LettuceRedisCommandsClient[K, V](
with LettuceRedisBaseAsyncCommands[K, V]
with LettuceRedisKeyAsyncCommands[K, V]
with LettuceRedisHashAsyncCommands[K, V]
with LettuceRedisHLLAsyncCommands[K, V]
with LettuceRedisServerAsyncCommands[K, V]
with LettuceRedisListAsyncCommands[K, V]
with LettuceRedisScriptingAsyncCommands[K, V]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.github.scoquelin.arugula.commands

import scala.concurrent.Future

import com.github.scoquelin.arugula.internal.LettuceRedisCommandDelegation

trait LettuceRedisHLLAsyncCommands[K, V] extends RedisHLLAsyncCommands[K, V] with LettuceRedisCommandDelegation[K, V] {
override def pfAdd(key: K, values: V*): Future[Long] =
delegateRedisClusterCommandAndLift(_.pfadd(key, values: _*)).map(Long2long)

override def pfMerge(destinationKey: K, sourceKeys: K*): Future[Unit] =
delegateRedisClusterCommandAndLift(_.pfmerge(destinationKey, sourceKeys: _*)).map(_ => ())

override def pfCount(keys: K*): Future[Long] =
delegateRedisClusterCommandAndLift(_.pfcount(keys: _*)).map(Long2long)
}
Original file line number Diff line number Diff line change
Expand Up @@ -1036,6 +1036,25 @@ class RedisCommandsIntegrationSpec extends BaseRedisCommandsIntegrationSpec with
}
}

"leveraging RedisHLLAsyncCommands" should {
"add, count and merge HyperLogLog keys" in {
withRedisSingleNodeAndCluster(RedisCodec.Utf8WithValueAsStringCodec) { client =>

val key1 = randomKey("hll-key1", suffix = "{user1}")
val key2 = randomKey("hll-key2", suffix = "{user1}")
for {
_ <- client.pfAdd(key1, "a", "b", "c", "d", "e")
_ <- client.pfAdd(key2, "a", "b", "f", "g", "h")
count <- client.pfCount(key1, key2)
_ <- count shouldBe 8L
_ <- client.pfMerge(key1, key2)
count <- client.pfCount(key1)
_ <- count shouldBe 8L
} yield succeed
}
}
}

"leveraging RedisSetAsyncCommands" should {
"create, retrieve, pop, and remove values in a set" in {
withRedisSingleNodeAndCluster(RedisCodec.Utf8WithValueAsStringCodec) { client =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.github.scoquelin.arugula

import io.lettuce.core.RedisFuture
import org.mockito.Mockito.{verify, when}
import org.scalatest.matchers.must.Matchers
import org.scalatest.{FutureOutcome, wordspec}

class LettuceRedisHLLAsyncCommandsSpec extends wordspec.FixtureAsyncWordSpec with Matchers {

override type FixtureParam = LettuceRedisCommandsClientFixture.TestContext

override def withFixture(test: OneArgAsyncTest): FutureOutcome =
withFixture(test.toNoArgAsyncTest(new LettuceRedisCommandsClientFixture.TestContext))

"LettuceRedisHLLAsyncCommands" should {
"delegate PFADD 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.pfadd("key", "value")).thenReturn(mockRedisFuture)

testClass.pfAdd("key", "value").map { result =>
result mustBe expectedValue
verify(lettuceAsyncCommands).pfadd("key", "value")
succeed
}
}

"delegate PFMERGE command to Lettuce and lift result into a Future" in { testContext =>
import testContext._

val mockRedisFuture: RedisFuture[String] = mockRedisFutureToReturn("OK")
when(lettuceAsyncCommands.pfmerge("destinationKey", "sourceKey")).thenReturn(mockRedisFuture)

testClass.pfMerge("destinationKey", "sourceKey").map { result =>
result mustBe ()
verify(lettuceAsyncCommands).pfmerge("destinationKey", "sourceKey")
succeed
}
}

"delegate PFCOUNT 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.pfcount("key")).thenReturn(mockRedisFuture)

testClass.pfCount("key").map { result =>
result mustBe expectedValue
verify(lettuceAsyncCommands).pfcount("key")
succeed
}
}
}
}

0 comments on commit c4eb2ed

Please sign in to comment.