Skip to content

Commit

Permalink
Scripting API (scriptDebug, scriptFlush, scriptKill) (zio#750)
Browse files Browse the repository at this point in the history
  • Loading branch information
drmarjanovic authored Feb 8, 2023
1 parent 592b087 commit 3dee3ae
Show file tree
Hide file tree
Showing 14 changed files with 171 additions and 15 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ object ZIORedisExample extends ZIOAppDefault {
} yield ()

override def run = myApp.provide(
RedisLive.layer,
Redis.layer,
RedisExecutor.layer,
ZLayer.succeed(RedisConfig.Default),
ZLayer.succeed[BinaryCodec](ProtobufCodec)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ object BenchmarkRuntime {
ZLayer.make[Redis](
RedisExecutor.local,
ZLayer.succeed[BinaryCodec](ProtobufCodec),
RedisLive.layer
Redis.layer
)
}
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ object ZIORedisExample extends ZIOAppDefault {
} yield ()

override def run = myApp.provide(
RedisLive.layer,
Redis.layer,
RedisExecutor.layer,
ZLayer.succeed(RedisConfig.Default),
ZLayer.succeed[BinaryCodec](ProtobufCodec)
Expand Down
4 changes: 2 additions & 2 deletions example/src/main/scala/example/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import example.config.AppConfig
import sttp.client3.httpclient.zio.HttpClientZioBackend
import zhttp.service.Server
import zio._
import zio.redis.{RedisExecutor, RedisLive}
import zio.redis.{Redis, RedisExecutor}
import zio.schema.codec.{BinaryCodec, ProtobufCodec}

object Main extends ZIOAppDefault {
Expand All @@ -33,7 +33,7 @@ object Main extends ZIOAppDefault {
ContributorsCache.layer,
HttpClientZioBackend.layer(),
RedisExecutor.layer,
RedisLive.layer,
Redis.layer,
ZLayer.succeed[BinaryCodec](ProtobufCodec)
)
.exitCode
Expand Down
2 changes: 1 addition & 1 deletion redis/src/main/scala/zio/redis/ClusterExecutor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ object ClusterExecutor {
private def redis(address: RedisUri) = {
val executorLayer = ZLayer.succeed(RedisConfig(address.host, address.port)) >>> RedisExecutor.layer
val codecLayer = ZLayer.succeed[BinaryCodec](StringUtf8Codec)
val redisLayer = executorLayer ++ codecLayer >>> RedisLive.layer
val redisLayer = executorLayer ++ codecLayer >>> Redis.layer
for {
closableScope <- Scope.make
layer <- closableScope.extend[Any](redisLayer.memoize)
Expand Down
5 changes: 5 additions & 0 deletions redis/src/main/scala/zio/redis/Input.scala
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,11 @@ object Input {
Chunk.single(encodeString(data.stringify))
}

case object ScriptFlushInput extends Input[FlushMode] {
def encode(data: FlushMode)(implicit codec: BinaryCodec): Chunk[BulkString] =
Chunk.single(encodeString(data.stringify))
}

case object WithScoresInput extends Input[WithScores] {
def encode(data: WithScores)(implicit codec: BinaryCodec): Chunk[RespValue.BulkString] =
Chunk.single(encodeString(data.stringify))
Expand Down
13 changes: 9 additions & 4 deletions redis/src/main/scala/zio/redis/Redis.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,14 @@ trait Redis
def executor: RedisExecutor
}

final case class RedisLive(codec: BinaryCodec, executor: RedisExecutor) extends Redis

object RedisLive {
object Redis {
lazy val layer: URLayer[RedisExecutor with BinaryCodec, Redis] =
ZLayer.fromFunction(RedisLive.apply _)
ZLayer {
for {
executor <- ZIO.service[RedisExecutor]
codec <- ZIO.service[BinaryCodec]
} yield Live(codec, executor)
}

private final case class Live(codec: BinaryCodec, executor: RedisExecutor) extends Redis
}
41 changes: 41 additions & 0 deletions redis/src/main/scala/zio/redis/api/Scripting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ trait Scripting extends RedisEnvironment {
}
}

/**
* Set the debug mode for executed scripts.
*
* @param mode
* mode in which scripts debug is going to work ["YES", "SYNC", "NO"]
* @return
* the Unit value.
*/
def scriptDebug(mode: DebugMode): IO[RedisError, Unit] = {
val command = RedisCommand(ScriptDebug, ScriptDebugInput, UnitOutput, codec, executor)
command.run(mode)
}

/**
* Checks existence of the scripts in the script cache.
*
Expand All @@ -90,6 +103,31 @@ trait Scripting extends RedisEnvironment {
command.run((sha1, sha1s.toList))
}

/**
* Remove all the scripts from the script cache.
*
* @param mode
* mode in which script flush is going to be executed ["ASYNC", "SYNC"] Note: "SYNC" mode is used by default (if no
* mode is provided)
* @return
* the Unit value.
*/
def scriptFlush(mode: Option[FlushMode] = None): IO[RedisError, Unit] = {
val command = RedisCommand(ScriptFlush, OptionalInput(ScriptFlushInput), UnitOutput, codec, executor)
command.run(mode)
}

/**
* Kill the currently executing EVAL script, assuming no write operation was yet performed by the script.
*
* @return
* the Unit value.
*/
def scriptKill: IO[RedisError, Unit] = {
val command = RedisCommand(ScriptKill, NoInput, UnitOutput, codec, executor)
command.run(())
}

/**
* Loads a script into the scripts cache. After the script is loaded into the script cache it could be evaluated using
* the [[zio.redis.api.Scripting.evalSha]] method.
Expand All @@ -108,6 +146,9 @@ trait Scripting extends RedisEnvironment {
private[redis] object Scripting {
final val Eval = "EVAL"
final val EvalSha = "EVALSHA"
final val ScriptDebug = "SCRIPT DEBUG"
final val ScriptExists = "SCRIPT EXISTS"
final val ScriptFlush = "SCRIPT FLUSH"
final val ScriptKill = "SCRIPT KILL"
final val ScriptLoad = "SCRIPT LOAD"
}
13 changes: 13 additions & 0 deletions redis/src/main/scala/zio/redis/options/Scripting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,17 @@ trait Scripting {
case object Sync extends DebugMode
case object No extends DebugMode
}

sealed trait FlushMode { self =>
private[redis] final def stringify: String =
self match {
case FlushMode.Async => "ASYNC"
case FlushMode.Sync => "SYNC"
}
}

object FlushMode {
case object Async extends FlushMode
case object Sync extends FlushMode
}
}
4 changes: 2 additions & 2 deletions redis/src/test/scala/zio/redis/ApiSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ object ApiSpec
scriptingSpec
)

val Layer: Layer[Any, Redis] = ZLayer.make[Redis](RedisExecutor.local.orDie, ZLayer.succeed(codec), RedisLive.layer)
val Layer: Layer[Any, Redis] = ZLayer.make[Redis](RedisExecutor.local.orDie, ZLayer.succeed(codec), Redis.layer)
}

private object Cluster {
Expand All @@ -65,7 +65,7 @@ object ApiSpec
ZLayer.succeed(RedisClusterConfig(Chunk(RedisUri("localhost", 5000)))),
ClusterExecutor.layer,
ZLayer.succeed(codec),
RedisLive.layer
Redis.layer
)
}

Expand Down
4 changes: 2 additions & 2 deletions redis/src/test/scala/zio/redis/ClusterExecutorSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ object ClusterExecutorSpec extends BaseSpec {
ZLayer.succeed(RedisConfig(uri.host, uri.port)),
RedisExecutor.layer,
ZLayer.succeed(codec),
RedisLive.layer
Redis.layer
)

private val ClusterLayer: Layer[Any, Redis] = {
Expand All @@ -80,7 +80,7 @@ object ClusterExecutorSpec extends BaseSpec {
ZLayer.succeed(RedisClusterConfig(Chunk(address1, address2))),
ClusterExecutor.layer.orDie,
ZLayer.succeed(codec),
RedisLive.layer
Redis.layer
)
}
}
29 changes: 29 additions & 0 deletions redis/src/test/scala/zio/redis/InputSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,35 @@ object InputSpec extends BaseSpec {
} yield assert(result)(equalTo(respArgs("4.2", "5.2")))
}
),
suite("ScriptDebug")(
test("yes") {
for {
result <- ZIO.attempt(ScriptDebugInput.encode(DebugMode.Yes))
} yield assert(result)(equalTo(respArgs("YES")))
},
test("sync") {
for {
result <- ZIO.attempt(ScriptDebugInput.encode(DebugMode.Sync))
} yield assert(result)(equalTo(respArgs("SYNC")))
},
test("no") {
for {
result <- ZIO.attempt(ScriptDebugInput.encode(DebugMode.No))
} yield assert(result)(equalTo(respArgs("NO")))
}
),
suite("ScriptFlush")(
test("asynchronous") {
for {
result <- ZIO.attempt(ScriptFlushInput.encode(FlushMode.Async))
} yield assert(result)(equalTo(respArgs("ASYNC")))
},
test("synchronous") {
for {
result <- ZIO.attempt(ScriptFlushInput.encode(FlushMode.Sync))
} yield assert(result)(equalTo(respArgs("SYNC")))
}
),
suite("String")(
test("non-empty value") {
for {
Expand Down
2 changes: 1 addition & 1 deletion redis/src/test/scala/zio/redis/KeysSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ object KeysSpec {
RedisConnectionLive.layer,
SingleNodeExecutor.layer,
ZLayer.succeed[BinaryCodec](ProtobufCodec),
RedisLive.layer
Redis.layer
)
.fresh
}
63 changes: 63 additions & 0 deletions redis/src/test/scala/zio/redis/ScriptingSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,26 @@ trait ScriptingSpec extends BaseSpec {
} yield assert(res)(isLeft(isSubtype[NoScript](hasField("message", _.message, equalTo(error)))))
}
),
suite("scriptDebug")(
test("enable non-blocking asynchronous debugging") {
for {
redis <- ZIO.service[Redis]
res <- redis.scriptDebug(DebugMode.Yes)
} yield assert(res)(isUnit)
},
test("enable blocking synchronous debugging") {
for {
redis <- ZIO.service[Redis]
res <- redis.scriptDebug(DebugMode.Sync)
} yield assert(res)(isUnit)
},
test("disable debug mode") {
for {
redis <- ZIO.service[Redis]
res <- redis.scriptDebug(DebugMode.No)
} yield assert(res)(isUnit)
}
),
suite("scriptExists")(
test("return true if scripts are found in the cache") {
val lua1 = """return "1""""
Expand All @@ -183,6 +203,49 @@ trait ScriptingSpec extends BaseSpec {
} yield assertTrue(res == Chunk(false, false))
}
),
suite("scriptFlush")(
test("flush scripts in default mode") {
val lua1 = """return "1""""
val lua2 = """return "2""""
for {
redis <- ZIO.service[Redis]
sha1 <- redis.scriptLoad(lua1)
sha2 <- redis.scriptLoad(lua2)
_ <- redis.scriptFlush()
res <- redis.scriptExists(sha1, sha2)
} yield assertTrue(res == Chunk(false, false))
},
test("flush scripts in SYNC mode") {
val lua1 = """return "1""""
val lua2 = """return "2""""
for {
redis <- ZIO.service[Redis]
sha1 <- redis.scriptLoad(lua1)
sha2 <- redis.scriptLoad(lua2)
_ <- redis.scriptFlush(mode = Some(FlushMode.Sync))
res <- redis.scriptExists(sha1, sha2)
} yield assertTrue(res == Chunk(false, false))
},
test("flush scripts in ASYNC mode") {
val lua1 = """return "1""""
val lua2 = """return "2""""
for {
redis <- ZIO.service[Redis]
sha1 <- redis.scriptLoad(lua1)
sha2 <- redis.scriptLoad(lua2)
_ <- redis.scriptFlush(mode = Some(FlushMode.Async))
res <- redis.scriptExists(sha1, sha2)
} yield assertTrue(res == Chunk(false, false))
}
),
suite("scriptKill")(
test("return NOTBUSY when there is no scripts in execution") {
for {
redis <- ZIO.service[Redis]
res <- redis.scriptKill.either
} yield assert(res)(isLeft(isSubtype[RedisError.NotBusy](anything)))
}
),
suite("scriptLoad")(
test("return OK") {
val lua = """return "1""""
Expand Down

0 comments on commit 3dee3ae

Please sign in to comment.