Skip to content

Commit

Permalink
fix redis-rs#1066: impl Script::from_hash
Browse files Browse the repository at this point in the history
Allow for instantiation of a Script object given a hash.
  • Loading branch information
tomers committed Mar 5, 2024
1 parent 3867f3f commit 1bb618c
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 11 deletions.
42 changes: 31 additions & 11 deletions redis/src/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::Cmd;
/// Represents a lua script.
#[derive(Debug, Clone)]
pub struct Script {
code: String,
code: Option<String>,
hash: String,
}

Expand All @@ -34,11 +34,19 @@ impl Script {
let mut hash = Sha1::new();
hash.update(code.as_bytes());
Script {
code: code.to_string(),
code: Some(code.to_string()),
hash: hash.digest().to_string(),
}
}

/// Creates a new script object given hash.
pub fn from_hash(hash: &str) -> Script {
Script {
code: None,
hash: hash.to_string(),
}
}

/// Returns the script's SHA1 hash in hexadecimal format.
pub fn get_hash(&self) -> &str {
&self.hash
Expand Down Expand Up @@ -103,6 +111,12 @@ impl Script {
.invoke_async(con)
.await
}

/// Checks whether script is loadable.
#[inline]
pub fn is_loadable(&self) -> bool {
self.code.is_some()
}
}

/// Represents a prepared script call.
Expand Down Expand Up @@ -146,8 +160,8 @@ impl<'a> ScriptInvocation<'a> {
match eval_cmd.query(con) {
Ok(val) => Ok(val),
Err(err) => {
if err.kind() == ErrorKind::NoScriptError {
self.load_cmd().query(con)?;
if err.kind() == ErrorKind::NoScriptError && self.script.is_loadable() {
self.load_cmd()?.query(con)?;
eval_cmd.query(con)
} else {
Err(err)
Expand All @@ -172,8 +186,8 @@ impl<'a> ScriptInvocation<'a> {
}
Err(err) => {
// Load the script into Redis if the script hash wasn't there already
if err.kind() == ErrorKind::NoScriptError {
self.load_cmd().query_async(con).await?;
if err.kind() == ErrorKind::NoScriptError && self.script.is_loadable() {
self.load_cmd()?.query_async(con).await?;
eval_cmd.query_async(con).await
} else {
Err(err)
Expand All @@ -185,7 +199,7 @@ impl<'a> ScriptInvocation<'a> {
/// Loads the script and returns the SHA1 of it.
#[inline]
pub fn load(&self, con: &mut dyn ConnectionLike) -> RedisResult<String> {
let hash: String = self.load_cmd().query(con)?;
let hash: String = self.load_cmd()?.query(con)?;

debug_assert_eq!(hash, self.script.hash);

Expand All @@ -199,17 +213,23 @@ impl<'a> ScriptInvocation<'a> {
where
C: crate::aio::ConnectionLike,
{
let hash: String = self.load_cmd().query_async(con).await?;
let hash: String = self.load_cmd()?.query_async(con).await?;

debug_assert_eq!(hash, self.script.hash);

Ok(hash)
}

fn load_cmd(&self) -> Cmd {
fn load_cmd(&self) -> RedisResult<Cmd> {
let mut cmd = cmd("SCRIPT");
cmd.arg("LOAD").arg(self.script.code.as_bytes());
cmd
match &self.script.code {
Some(code) => cmd.arg("LOAD").arg(code.as_bytes()),
None => fail!((
ErrorKind::NoScriptError,
"Cannot load script object without content"
)),
};
Ok(cmd)
}

fn estimate_buflen(&self) -> usize {
Expand Down
28 changes: 28 additions & 0 deletions redis/tests/test_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,34 @@ fn test_script_load() {
assert_eq!(hash, Ok(script.get_hash().to_string()));
}

#[test]
#[cfg(feature = "script")]
fn test_script_from_hash() {
let ctx = TestContext::new();
let mut con = ctx.connection();

let script = redis::Script::new(
r"
return {redis.call('GET', KEYS[1]), ARGV[1]}
",
);

let hash = script.prepare_invoke().load(&mut con);

let script_from_hash = redis::Script::from_hash(hash.as_ref().unwrap());

assert_eq!(hash, Ok(script_from_hash.get_hash().to_string()));

let _: () = redis::cmd("SET")
.arg("my_key")
.arg("foo")
.query(&mut con)
.unwrap();
let response = script_from_hash.key("my_key").arg(42).invoke(&mut con);

assert_eq!(response, Ok(("foo".to_string(), 42)));
}

#[test]
fn test_tuple_args() {
let ctx = TestContext::new();
Expand Down

0 comments on commit 1bb618c

Please sign in to comment.