From f0965b6a1406ba31a816d51ad022b1bd7e0096ac Mon Sep 17 00:00:00 2001 From: Yisrael Union Date: Thu, 12 Sep 2024 15:24:10 -0400 Subject: [PATCH] add overloaded scan that handles Redis Type argument (#921) --- .../profunktor/redis4cats/algebra/keys.scala | 6 +++- .../dev/profunktor/redis4cats/effects.scala | 28 ++++++++++++++++++- .../dev/profunktor/redis4cats/redis.scala | 6 ++++ .../profunktor/redis4cats/TestScenarios.scala | 12 ++++---- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/modules/effects/src/main/scala/dev/profunktor/redis4cats/algebra/keys.scala b/modules/effects/src/main/scala/dev/profunktor/redis4cats/algebra/keys.scala index a553ecc7..b8d27710 100644 --- a/modules/effects/src/main/scala/dev/profunktor/redis4cats/algebra/keys.scala +++ b/modules/effects/src/main/scala/dev/profunktor/redis4cats/algebra/keys.scala @@ -18,7 +18,7 @@ package dev.profunktor.redis4cats.algebra import java.time.Instant import dev.profunktor.redis4cats.data.KeyScanCursor -import dev.profunktor.redis4cats.effects.{ CopyArgs, ExpireExistenceArg, RedisType, RestoreArgs, ScanArgs } +import dev.profunktor.redis4cats.effects.{ CopyArgs, ExpireExistenceArg, KeyScanArgs, RedisType, RestoreArgs, ScanArgs } import scala.concurrent.duration.FiniteDuration @@ -42,10 +42,14 @@ trait KeyCommands[F[_], K] { @deprecated("In favor of scan(cursor: KeyScanCursor[K])", since = "0.10.4") def scan(cursor: Long): F[KeyScanCursor[K]] def scan(previous: KeyScanCursor[K]): F[KeyScanCursor[K]] + @deprecated("In favor of scan(keyScanArgs: KeyScanArgs)", since = "1.7.2") def scan(scanArgs: ScanArgs): F[KeyScanCursor[K]] + def scan(keyScanArgs: KeyScanArgs): F[KeyScanCursor[K]] @deprecated("In favor of scan(cursor: KeyScanCursor[K], scanArgs: ScanArgs)", since = "0.10.4") def scan(cursor: Long, scanArgs: ScanArgs): F[KeyScanCursor[K]] + @deprecated("In favor of scan(previous: KeyScanCursor[K], keyScanArgs: KeyScanArgs)", since = "1.7.2") def scan(previous: KeyScanCursor[K], scanArgs: ScanArgs): F[KeyScanCursor[K]] + def scan(cursor: KeyScanCursor[K], keyScanArgs: KeyScanArgs): F[KeyScanCursor[K]] def typeOf(key: K): F[Option[RedisType]] def ttl(key: K): F[Option[FiniteDuration]] diff --git a/modules/effects/src/main/scala/dev/profunktor/redis4cats/effects.scala b/modules/effects/src/main/scala/dev/profunktor/redis4cats/effects.scala index 9bd18240..8b9262f8 100644 --- a/modules/effects/src/main/scala/dev/profunktor/redis4cats/effects.scala +++ b/modules/effects/src/main/scala/dev/profunktor/redis4cats/effects.scala @@ -18,7 +18,12 @@ package dev.profunktor.redis4cats import java.time.Instant -import io.lettuce.core.{ GeoArgs, ScriptOutputType => JScriptOutputType, ScanArgs => JScanArgs } +import io.lettuce.core.{ + GeoArgs, + ScriptOutputType => JScriptOutputType, + ScanArgs => JScanArgs, + KeyScanArgs => JKeyScanArgs +} import scala.concurrent.duration.FiniteDuration @@ -123,6 +128,27 @@ object effects { def apply(`match`: String, count: Long): ScanArgs = ScanArgs(Some(`match`), Some(count)) } + sealed abstract class KeyScanArgs(tpe: Option[RedisType], pattern: Option[String], count: Option[Long]) { + def underlying: JKeyScanArgs = { + val u = new JKeyScanArgs + pattern.foreach(u.`match`) + count.foreach(u.limit) + tpe.foreach(t => u.`type`(t.asString)) + u + } + } + + object KeyScanArgs { + def apply(pattern: String): KeyScanArgs = new KeyScanArgs(None, Some(pattern), None) {} + def apply(tpe: RedisType): KeyScanArgs = new KeyScanArgs(Some(tpe), None, None) {} + def apply(tpe: RedisType, pattern: String): KeyScanArgs = new KeyScanArgs(Some(tpe), Some(pattern), None) {} + def apply(count: Long): KeyScanArgs = new KeyScanArgs(None, None, Some(count)) {} + def apply(pattern: String, count: Long): KeyScanArgs = new KeyScanArgs(None, Some(pattern), Some(count)) {} + def apply(tpe: RedisType, count: Long): KeyScanArgs = new KeyScanArgs(Some(tpe), None, Some(count)) {} + def apply(tpe: RedisType, pattern: String, count: Long): KeyScanArgs = + new KeyScanArgs(Some(tpe), Some(pattern), Some(count)) {} + } + sealed trait FlushMode { def asJava: io.lettuce.core.FlushMode = this match { diff --git a/modules/effects/src/main/scala/dev/profunktor/redis4cats/redis.scala b/modules/effects/src/main/scala/dev/profunktor/redis4cats/redis.scala index 59c4878b..6ed88a81 100644 --- a/modules/effects/src/main/scala/dev/profunktor/redis4cats/redis.scala +++ b/modules/effects/src/main/scala/dev/profunktor/redis4cats/redis.scala @@ -568,6 +568,12 @@ private[redis4cats] class BaseRedis[F[_]: FutureLift: MonadThrow: Log, K, V]( override def scan(previous: KeyScanCursor[K], scanArgs: ScanArgs): F[KeyScanCursor[K]] = async.flatMap(_.scan(previous.underlying, scanArgs.underlying).futureLift.map(KeyScanCursor[K])) + override def scan(keyScanArgs: KeyScanArgs): F[KeyScanCursor[K]] = + async.flatMap(_.scan(keyScanArgs.underlying).futureLift.map(KeyScanCursor[K])) + + override def scan(cursor: KeyScanCursor[K], keyScanArgs: KeyScanArgs): F[KeyScanCursor[K]] = + async.flatMap(_.scan(cursor.underlying, keyScanArgs.underlying).futureLift.map(KeyScanCursor[K])) + override def ttl(key: K): F[Option[FiniteDuration]] = async.flatMap(_.ttl(key).futureLift.map(toFiniteDuration(TimeUnit.SECONDS))) diff --git a/modules/tests/src/test/scala/dev/profunktor/redis4cats/TestScenarios.scala b/modules/tests/src/test/scala/dev/profunktor/redis4cats/TestScenarios.scala index 88724aeb..40ed4c50 100644 --- a/modules/tests/src/test/scala/dev/profunktor/redis4cats/TestScenarios.scala +++ b/modules/tests/src/test/scala/dev/profunktor/redis4cats/TestScenarios.scala @@ -328,10 +328,12 @@ trait TestScenarios { self: FunSuite => scan0 <- redis.scan _ <- IO(assertEquals(scan0.cursor, "0")) _ <- IO(assertEquals(scan0.keys.sorted, keys)) - scan1 <- redis.scan(ScanArgs(1)) + scan00 <- redis.scan(KeyScanArgs(RedisType.Hash)) + _ <- IO(assertEquals(scan00.cursor, "0")) + scan1 <- redis.scan(KeyScanArgs(RedisType.String, 1)) _ <- IO(assert(scan1.keys.nonEmpty, "read at least something but no hard requirement")) _ <- IO(assert(scan1.keys.size < keys.size, "but read less than all of them")) - scan2 <- redis.scan(scan1, ScanArgs("key*")) + scan2 <- redis.scan(scan1, KeyScanArgs("key*")) _ <- IO(assertEquals(scan2.cursor, "0")) _ <- IO(assertEquals((scan1.keys ++ scan2.keys).sorted, keys, "read to the end in result")) } yield () @@ -344,11 +346,11 @@ trait TestScenarios { self: FunSuite => tp <- clusterScan(redis, args = None) (keys0, iterations0) = tp _ <- IO(assertEquals(keys0.sorted, keys)) - tp <- clusterScan(redis, args = Some(ScanArgs("key*"))) + tp <- clusterScan(redis, args = Some(KeyScanArgs("key*"))) (keys1, iterations1) = tp _ <- IO(assertEquals(keys1.sorted, keys)) _ <- IO(assertEquals(iterations1, iterations0)) - tp <- clusterScan(redis, args = Some(ScanArgs(1))) + tp <- clusterScan(redis, args = Some(KeyScanArgs(1))) (keys2, iterations2) = tp _ <- IO(assertEquals(keys2.sorted, keys)) _ <- IO(assert(iterations2 > iterations0, "made more iterations because of limit")) @@ -362,7 +364,7 @@ trait TestScenarios { self: FunSuite => */ private def clusterScan( redis: RedisCommands[IO, String, String], - args: Option[ScanArgs] + args: Option[KeyScanArgs] ): IO[(List[String], Iterations)] = { def scanRec(previous: KeyScanCursor[String], acc: List[String], cnt: Int): IO[(List[String], Iterations)] = if (previous.isFinished) IO.pure((previous.keys ++ acc, cnt))