Skip to content

Commit

Permalink
Merge pull request #49 from scoquelin/jl-scripting-poc
Browse files Browse the repository at this point in the history
support eval scripting commands
  • Loading branch information
72squared authored Sep 4, 2024
2 parents 90cfd5d + 9304c35 commit b4fe04d
Show file tree
Hide file tree
Showing 6 changed files with 588 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ trait RedisCommandsClient[K, V]
with RedisListAsyncCommands[K, V]
with RedisSetAsyncCommands[K, V]
with RedisSortedSetAsyncCommands[K, V]
with RedisScriptingAsyncCommands[K, V]
with RedisServerAsyncCommands[K, V]
with RedisPipelineAsyncCommands[K, V]
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package com.github.scoquelin.arugula.commands

import scala.concurrent.Future
import scala.jdk.CollectionConverters.CollectionHasAsScala

import com.github.scoquelin.arugula.commands.RedisScriptingAsyncCommands.ScriptOutputType

trait RedisScriptingAsyncCommands[K, V] {
/**
* Evaluate a script
* @param script The script to evaluate
* @param outputType The expected output type
* @param keys The keys to use in the script
* @return The result of the script
*/
def eval(script: String, outputType: ScriptOutputType, keys:K*): Future[outputType.R]

/**
* Evaluate a script
* @param script The script to evaluate
* @param outputType The expected output type
* @param keys The keys to use in the script
* @return The result of the script
*/
def eval(script: Array[Byte], outputType: ScriptOutputType, keys:K*): Future[outputType.R]

/**
* Evaluate a script
* @param script The script to evaluate
* @param outputType The expected output type
* @param keys The keys to use in the script
* @param values The values to use in the script
* @return The result of the script
*/
def eval(script: String, outputType: ScriptOutputType, keys: List[K], values: V*): Future[outputType.R]

/**
* Evaluate a script
* @param script The script to evaluate
* @param outputType The expected output type
* @param keys The keys to use in the script
* @param values The values to use in the script
* @return The result of the script
*/
def eval(script: Array[Byte], outputType: ScriptOutputType, keys: List[K], values: V*): Future[outputType.R]

/**
* Evaluate a script by its SHA
* @param sha The SHA of the script to evaluate
* @param outputType The expected output type
* @param keys The keys to use in the script
* @return The result of the script
*/
def evalSha(sha: String, outputType: ScriptOutputType, keys:K*): Future[outputType.R]

/**
* Evaluate a script by its SHA
* @param sha The SHA of the script to evaluate
* @param outputType The expected output type
* @param keys The keys to use in the script
* @return The result of the script
*/
def evalSha(sha: String, outputType: ScriptOutputType, keys: List[K], values: V*): Future[outputType.R]

/**
* Evaluate a script in read-only mode
* @param script The script to evaluate
* @param outputType The expected output type
* @param keys The keys to use in the script
* @param values The values to use in the script
* @return The result of the script
*/
def evalReadOnly(script: String, outputType: ScriptOutputType, keys:List[K], values: V*): Future[outputType.R]

/**
* Evaluate a script in read-only mode
* @param script The script to evaluate
* @param outputType The expected output type
* @param keys The keys to use in the script
* @param values The values to use in the script
* @return The result of the script
*/
def evalReadOnly(script: Array[Byte], outputType: ScriptOutputType, keys:List[K], values: V*): Future[outputType.R]

/**
* check if a script exists based on its SHA
* @param digest The SHA of the script to check
* @return True if the script exists, false otherwise
*/
def scriptExists(digest: String): Future[Boolean]

/**
* check if a script exists based on its SHA
* @param digests The SHA of the scripts to check
* @return A list of booleans indicating if the scripts exist
*/
def scriptExists(digests: List[String]): Future[List[Boolean]]

/**
* Flush all scripts from the script cache
* @return Unit
*/
def scriptFlush: Future[Unit]

/**
* Flush all scripts from the script cache
* @param mode The mode to use for flushing
* @return Unit
*/
def scriptFlush(mode: RedisScriptingAsyncCommands.FlushMode): Future[Unit]

/**
* Kill the currently executing script
* @return Unit
*/
def scriptKill: Future[Unit]

/**
* Load a script into the script cache
* @param script The script to load
* @return The SHA of the script
*/
def scriptLoad(script: String): Future[String]

/**
* Load a script into the script cache
* @param script The script to load
* @return The SHA of the script
*/
def scriptLoad(script: Array[Byte]): Future[String]
}

object RedisScriptingAsyncCommands{

sealed trait ScriptOutputType {
type R

def convert(in: Any): R
}
object ScriptOutputType {
case object Boolean extends ScriptOutputType{
type R = Boolean

def convert(in: Any): R = in match {
case 0L => false
case 1L => true
case b: Boolean => b
case s: String => s.toBoolean
case _ => throw new IllegalArgumentException(s"Cannot convert $in to Boolean")
}
}
case object Integer extends ScriptOutputType{
type R = Long

def convert(in: Any): R = in match {
case l: Long => l
case i: Int => i.toLong
case s: String => s.toLong
case _ => throw new IllegalArgumentException(s"Cannot convert $in to Int")
}

}
case object Multi extends ScriptOutputType{
type R = List[Any]

def convert(in: Any): R = in match {
case l: List[_] => l
case l: java.util.List[_] => l.asScala.toList
case _ => throw new IllegalArgumentException(s"Cannot convert $in to List")
}
}
case object Status extends ScriptOutputType{
type R = String

def convert(in: Any): R = in match {
case s: String => s
case _ => throw new IllegalArgumentException(s"Cannot convert $in to String")
}
}
case object Value extends ScriptOutputType{
type R = Any

def convert(in: Any): R = in
}
}

sealed trait FlushMode

object FlushMode{
case object Async extends FlushMode
case object Sync extends FlushMode
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ private[arugula] class LettuceRedisCommandsClient[K, V](
with LettuceRedisHashAsyncCommands[K, V]
with LettuceRedisServerAsyncCommands[K, V]
with LettuceRedisListAsyncCommands[K, V]
with LettuceRedisScriptingAsyncCommands[K, V]
with LettuceRedisStringAsyncCommands[K, V]
with LettuceRedisSetAsyncCommands[K, V]
with LettuceRedisSortedSetAsyncCommands[K, V]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.github.scoquelin.arugula.commands

import scala.concurrent.Future
import scala.jdk.CollectionConverters._

import com.github.scoquelin.arugula.RedisCommandsClient
import com.github.scoquelin.arugula.commands.RedisScriptingAsyncCommands.ScriptOutputType
import com.github.scoquelin.arugula.internal.LettuceRedisCommandDelegation


trait LettuceRedisScriptingAsyncCommands[K, V] extends RedisScriptingAsyncCommands[K, V] with LettuceRedisCommandDelegation[K, V] { this: RedisCommandsClient[K, V] =>


override def eval(script: String, outputType: ScriptOutputType, keys:K*): Future[outputType.R] = {
delegateRedisClusterCommandAndLift(_.eval(
script,
LettuceRedisScriptingAsyncCommands.outputTypeToJava(outputType),
keys:_*)).map(outputType.convert)
}

override def eval(script: Array[Byte], outputType: ScriptOutputType, keys: K*): Future[outputType.R] = {
delegateRedisClusterCommandAndLift(_.eval(
script,
LettuceRedisScriptingAsyncCommands.outputTypeToJava(outputType),
keys:_*)).map(outputType.convert)
}

override def eval(script: String, outputType: ScriptOutputType, keys: List[K], values: V*): Future[outputType.R] = {
delegateRedisClusterCommandAndLift(_.eval(
script,
LettuceRedisScriptingAsyncCommands.outputTypeToJava(outputType),
keys.toArray[Any].asInstanceOf[Array[K with AnyRef]],
values:_*)).map(outputType.convert)
}

override def eval(script: Array[Byte], outputType: ScriptOutputType, keys: List[K], values: V*): Future[outputType.R] = {
delegateRedisClusterCommandAndLift(_.eval(
script,
LettuceRedisScriptingAsyncCommands.outputTypeToJava(outputType),
keys.toArray[Any].asInstanceOf[Array[K with AnyRef]],
values: _*)).map(outputType.convert)
}

override def evalSha(sha: String, outputType: ScriptOutputType, keys:K*): Future[outputType.R] = {
delegateRedisClusterCommandAndLift(_.evalsha(
sha,
LettuceRedisScriptingAsyncCommands.outputTypeToJava(outputType),
keys:_*)).map(outputType.convert)
}

override def evalSha(sha: String, outputType: ScriptOutputType, keys: List[K], values: V*): Future[outputType.R] = {
delegateRedisClusterCommandAndLift(_.evalsha(
sha,
LettuceRedisScriptingAsyncCommands.outputTypeToJava(outputType),
keys.toArray[Any].asInstanceOf[Array[K with AnyRef]],
values:_*)).map(outputType.convert)
}

override def evalReadOnly(script: String, outputType: ScriptOutputType, keys:List[K], values: V*): Future[outputType.R] = {
delegateRedisClusterCommandAndLift(_.evalReadOnly(
script.getBytes,
LettuceRedisScriptingAsyncCommands.outputTypeToJava(outputType),
keys.toArray[Any].asInstanceOf[Array[K with AnyRef]],
values:_*
)).map(outputType.convert)
}

override def evalReadOnly(script: Array[Byte], outputType: ScriptOutputType, keys:List[K], values: V*): Future[outputType.R] = {
delegateRedisClusterCommandAndLift(_.evalReadOnly(
script,
LettuceRedisScriptingAsyncCommands.outputTypeToJava(outputType),
keys.toArray[Any].asInstanceOf[Array[K with AnyRef]],
values:_*
)).map(outputType.convert)
}

override def scriptExists(digest: String): Future[Boolean] =
delegateRedisClusterCommandAndLift(_.scriptExists(digest)).map {
_.asScala.headOption.exists(Boolean2boolean)
}

override def scriptExists(digests: List[String]): Future[List[Boolean]] = {
delegateRedisClusterCommandAndLift(_.scriptExists(digests:_*)).map(_.asScala.map(Boolean2boolean).toList)
}

override def scriptFlush: Future[Unit] = {
delegateRedisClusterCommandAndLift(_.scriptFlush()).map(_ => ())
}

override def scriptFlush(mode: RedisScriptingAsyncCommands.FlushMode): Future[Unit] = {
delegateRedisClusterCommandAndLift(_.scriptFlush(
mode match {
case RedisScriptingAsyncCommands.FlushMode.Async => io.lettuce.core.FlushMode.ASYNC
case RedisScriptingAsyncCommands.FlushMode.Sync => io.lettuce.core.FlushMode.SYNC
}
)).map(_ => ())
}

override def scriptKill: Future[Unit] =
delegateRedisClusterCommandAndLift(_.scriptKill()).map(_ => ())

override def scriptLoad(script: String): Future[String] = {
delegateRedisClusterCommandAndLift(_.scriptLoad(script))
}

override def scriptLoad(script: Array[Byte]): Future[String] =
delegateRedisClusterCommandAndLift(_.scriptLoad(script))

}

object LettuceRedisScriptingAsyncCommands{
private[commands] def outputTypeToJava(outputType: ScriptOutputType): io.lettuce.core.ScriptOutputType = outputType match {
case ScriptOutputType.Boolean => io.lettuce.core.ScriptOutputType.BOOLEAN
case ScriptOutputType.Integer => io.lettuce.core.ScriptOutputType.INTEGER
case ScriptOutputType.Multi => io.lettuce.core.ScriptOutputType.MULTI
case ScriptOutputType.Status => io.lettuce.core.ScriptOutputType.STATUS
case ScriptOutputType.Value => io.lettuce.core.ScriptOutputType.VALUE
}
}
Loading

0 comments on commit b4fe04d

Please sign in to comment.