use crate::{
clients::RedisClient,
interfaces::{FunctionInterface, LuaInterface},
prelude::{FromRedis, RedisError, RedisResult},
types::{MultipleKeys, MultipleValues, RedisValue},
};
#[cfg(feature = "sha-1")]
use crate::{prelude::RedisErrorKind, util::sha1_hash};
use bytes_utils::Str;
use std::{
cmp::Ordering,
collections::HashMap,
convert::TryInto,
fmt,
fmt::Formatter,
hash::{Hash, Hasher},
ops::Deref,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Script {
lua: Option<Str>,
hash: Str,
}
impl fmt::Display for Script {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.hash)
}
}
impl Hash for Script {
fn hash<H: Hasher>(&self, state: &mut H) {
self.hash.hash(state);
}
}
impl Ord for Script {
fn cmp(&self, other: &Self) -> Ordering {
self.hash.cmp(&other.hash)
}
}
impl PartialOrd for Script {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Script {
#[cfg(feature = "sha-1")]
#[cfg_attr(docsrs, doc(cfg(feature = "sha-1")))]
pub fn from_lua<S: Into<Str>>(lua: S) -> Self {
let lua: Str = lua.into();
let hash = Str::from(sha1_hash(&lua));
Script { lua: Some(lua), hash }
}
pub fn from_hash<S: Into<Str>>(hash: S) -> Self {
Script {
lua: None,
hash: hash.into(),
}
}
pub fn lua(&self) -> Option<&Str> {
self.lua.as_ref()
}
pub fn sha1(&self) -> &Str {
&self.hash
}
#[cfg(feature = "sha-1")]
#[cfg_attr(docsrs, doc(cfg(feature = "sha-1")))]
pub async fn load(&self, client: &RedisClient) -> RedisResult<()> {
if let Some(ref lua) = self.lua {
client.script_load_cluster::<(), _>(lua.clone()).await
} else {
Err(RedisError::new(RedisErrorKind::Unknown, "Missing lua script contents."))
}
}
pub async fn evalsha<R, C, K, V>(&self, client: &C, keys: K, args: V) -> RedisResult<R>
where
R: FromRedis,
C: LuaInterface + Send + Sync,
K: Into<MultipleKeys> + Send,
V: TryInto<MultipleValues> + Send,
V::Error: Into<RedisError> + Send,
{
client.evalsha(self.hash.clone(), keys, args).await
}
#[cfg(feature = "sha-1")]
#[cfg_attr(docsrs, doc(cfg(feature = "sha-1")))]
pub async fn evalsha_with_reload<R, K, V>(&self, client: &RedisClient, keys: K, args: V) -> RedisResult<R>
where
R: FromRedis,
K: Into<MultipleKeys> + Send,
V: TryInto<MultipleValues> + Send,
V::Error: Into<RedisError> + Send,
{
into!(keys);
try_into!(args);
match client.evalsha(self.hash.clone(), keys.clone(), args.clone()).await {
Err(error) if error.details().starts_with("NOSCRIPT") => {
self.load(client).await?;
client.evalsha(self.hash.clone(), keys, args).await
},
result => result,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum FunctionFlag {
NoWrites,
AllowOOM,
NoCluster,
AllowCrossSlotKeys,
AllowStale,
}
impl FunctionFlag {
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Option<Self> {
Some(match s {
"allow-oom" => FunctionFlag::AllowOOM,
"allow-stale" => FunctionFlag::AllowStale,
"allow-cross-slot-keys" => FunctionFlag::AllowCrossSlotKeys,
"no-writes" => FunctionFlag::NoWrites,
"no-cluster" => FunctionFlag::NoCluster,
_ => return None,
})
}
pub fn to_str(&self) -> &'static str {
match self {
FunctionFlag::AllowCrossSlotKeys => "allow-cross-slot-keys",
FunctionFlag::AllowOOM => "allow-oom",
FunctionFlag::NoCluster => "no-cluster",
FunctionFlag::NoWrites => "no-writes",
FunctionFlag::AllowStale => "allow-stale",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Function {
pub(crate) name: Str,
pub(crate) flags: Vec<FunctionFlag>,
}
impl fmt::Display for Function {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)
}
}
impl Hash for Function {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
}
}
impl Ord for Function {
fn cmp(&self, other: &Self) -> Ordering {
self.name.cmp(&other.name)
}
}
impl PartialOrd for Function {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Function {
pub fn new<S: Into<Str>>(name: S, flags: Vec<FunctionFlag>) -> Self {
Function {
name: name.into(),
flags,
}
}
pub fn name(&self) -> &Str {
&self.name
}
pub fn flags(&self) -> &[FunctionFlag] {
&self.flags
}
pub async fn fcall<R, C, K, V>(&self, client: &C, keys: K, args: V) -> RedisResult<R>
where
R: FromRedis,
C: FunctionInterface + Send + Sync,
K: Into<MultipleKeys> + Send,
V: TryInto<MultipleValues> + Send,
V::Error: Into<RedisError> + Send,
{
client.fcall(self.name.clone(), keys, args).await
}
pub async fn fcall_ro<R, C, K, V>(&self, client: &C, keys: K, args: V) -> RedisResult<R>
where
R: FromRedis,
C: FunctionInterface + Send + Sync,
K: Into<MultipleKeys> + Send,
V: TryInto<MultipleValues> + Send,
V::Error: Into<RedisError> + Send,
{
client.fcall_ro(self.name.clone(), keys, args).await
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Library {
name: Str,
functions: HashMap<Str, Function>,
}
impl fmt::Display for Library {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)
}
}
impl Hash for Library {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
}
}
impl Ord for Library {
fn cmp(&self, other: &Self) -> Ordering {
self.name.cmp(&other.name)
}
}
impl PartialOrd for Library {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Library {
pub async fn from_code<S>(client: &RedisClient, code: S) -> Result<Self, RedisError>
where
S: Into<Str>,
{
let code = code.into();
let name: Str = client.function_load_cluster(true, code).await?;
let functions = client
.function_list::<RedisValue, _>(Some(name.deref()), false)
.await?
.as_functions(&name)?;
Ok(Library {
name,
functions: functions.into_iter().map(|f| (f.name.clone(), f)).collect(),
})
}
pub async fn from_name<S>(client: &RedisClient, name: S) -> Result<Self, RedisError>
where
S: Into<Str>,
{
let name = name.into();
let functions = client
.function_list::<RedisValue, _>(Some(name.deref()), false)
.await?
.as_functions(&name)?;
Ok(Library {
name,
functions: functions.into_iter().map(|f| (f.name.clone(), f)).collect(),
})
}
pub fn name(&self) -> &Str {
&self.name
}
pub fn functions(&self) -> &HashMap<Str, Function> {
&self.functions
}
}