Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support hyperloglog commands #50

Merged
merged 1 commit into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
}
}
}