1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
//! Miscellaneous Redis utilities and helper functions.
use redis::{cmd, ToRedisArgs, FromRedisValue, RedisResult, Value, Pipeline, ConnectionLike, from_redis_value};
use crate::models::OcyResult;
/// Helper function for getting nested data structures from Redis pipelines.
///
/// Used for e.g. querying for vectors of tuples from:
/// pipe.hget(key1, [x, y, z])
/// .hget(key2, [x, y, z])
/// .hget(key3, [x, y, z])
///
/// let (a, b, c): Vec<(x_type, y_type, z_type)> = vec_from_redis_pipe(pipe, conn)?;
pub fn vec_from_redis_pipe<T: FromRedisValue>(
pipe: &Pipeline,
conn: &ConnectionLike
) -> RedisResult<Vec<T>> {
let values: Vec<Value> = pipe.query(conn)?;
let mut results = Vec::with_capacity(values.len());
for v in values {
results.push(from_redis_value::<T>(&v)?);
}
Ok(results)
}
/// Helper function to perform transactions in Redis. Based on the `redis::transaction` implementation,
/// but doesn't restrict the return type with `FromRedisValue`.
///
/// Takes a function that returns an `Option<T>`. A return value of `Some(T)` means that the transaction
/// succeeded, and that the loop should be terminated. A value of `None` means that a watched key was
/// modified during the transaction, and that the transaction should be retried.
use std::fmt::Debug;
pub fn transaction<
K: ToRedisArgs + Debug,
T,
F: FnMut(&mut Pipeline) -> OcyResult<Option<T>>
>(
conn: &ConnectionLike,
keys: &[K],
func: F,
) -> OcyResult<T> {
let mut func = func;
loop {
cmd("WATCH").arg(keys).query(conn)?;
let mut p = redis::pipe();
if let Some(result) = func(p.atomic())? {
// ensure no watch is left in connection, regardless of whether pipeline was used
cmd("UNWATCH").query(conn)?;
return Ok(result);
}
}
}