Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added hash_get macro to allow getting multiple hash fields at once #100

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,28 @@ impl RedisKey {
};
Ok(val)
}

/// Returns the internal `*mut raw::RedisModuleCtx` for this key
///
/// # Safety
///
/// This is intended for use by macros only. It returns a raw pointer to internal
/// state. Modification or incorrect usage of this value may cause undefined
/// behaviour.
pub unsafe fn get_ctx(&self) -> *mut raw::RedisModuleCtx {
self.ctx
}

/// Returns the internal `*mut raw::RedisModuleKey` for this key
///
/// # Safety
///
/// This is intended for use by macros only. It returns a raw pointer to internal
/// state. Modification or incorrect usage of this value may cause undefined
/// behaviour.
pub unsafe fn get_inner(&self) -> *mut raw::RedisModuleKey {
self.key_inner
}
}

impl Drop for RedisKey {
Expand Down Expand Up @@ -220,6 +242,28 @@ impl RedisKeyWritable {

status.into()
}

/// Returns the internal `*mut raw::RedisModuleCtx` for this key
///
/// # Safety
///
/// This is intended for use by macros only. It returns a raw pointer to internal
/// state. Modification or incorrect usage of this value may cause undefined
/// behaviour.
pub unsafe fn get_ctx(&self) -> *mut raw::RedisModuleCtx {
self.ctx
}

/// Returns the internal `*mut raw::RedisModuleKey` for this key
///
/// # Safety
///
/// This is intended for use by macros only. It returns a raw pointer to internal
/// state. Modification or incorrect usage of this value may cause undefined
/// behaviour.
pub unsafe fn get_inner(&self) -> *mut raw::RedisModuleKey {
self.key_inner
}
}

impl From<raw::Status> for Result<(), RedisError> {
Expand Down
81 changes: 81 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,84 @@ macro_rules! redis_module {
}
}
}

/// Gets one or more fields from a hash value, and returns them in a
/// `HashMap<String, RedisString>`. The field names may be provided as string slices
/// or as `String` objects. The returned HashMap will contain an entry for each field
/// that exists in the key. If the key itself does not exist, the HashMap will be empty.
///
/// # Examples
///
/// ```
/// let k: RedisKey = ctx.open_key("config");
/// let cfg: HashMap<String, RedisString> =
/// hash_get!(k, "hostname", "port");
/// let hostname: &str = cfg.get("hostname")
/// .map_or("localhost", |rs| rs.try_as_str().unwrap());
/// let port: &str = cfg.get("port")
/// .map_or("443", |rs| rs.try_as_str().unwrap());
/// ```
#[macro_export]
macro_rules! hash_get {
(@replace_expr $_t:tt $sub:expr) => { $sub };
(@count_tts $($tts:tt)*) => { 0usize $(+ hash_get!(@replace_expr $tts 1usize))* };

(@call ($key:expr) ($iter:ident) () -> ($($body:tt)*)) => {
unsafe {
use redis_module::redisraw::bindings::{RedisModule_HashGet, REDISMODULE_HASH_CFIELDS};
use std::ffi::CString;

RedisModule_HashGet.unwrap()(
$key,
REDISMODULE_HASH_CFIELDS as i32,
$($body)*
std::ptr::null::<std::os::raw::c_char>()
)
}
};
(@call ($key:expr) ($iter:ident) ($field:tt $($tail:tt)*) -> ($($body:tt)*)) => {
hash_get!(@call ($key) ($iter) ($($tail)*) ->
($($body)*
CString::new($field).unwrap().as_ptr(),
$iter.next().unwrap(),)
)
};

($key:ident, $($field:tt),*) => {
if !$key.is_null() {
use redis_module::raw::{RedisModuleString, Status};
use redis_module::redisraw::bindings::{RedisModule_HashGet, REDISMODULE_HASH_CFIELDS};
use redis_module::{RedisString, RedisError};
use std::collections::HashMap;

const LEN: usize = hash_get!(@count_tts $($field)*);
let mut values: [*mut RedisModuleString; LEN] = [std::ptr::null_mut(); LEN];
let res = Status::from({
let mut ivalues = values.iter_mut();
let key_inner = unsafe { $key.get_inner() };
hash_get!(@call (key_inner) (ivalues) ($($field)*) -> ())
});
if res == Status::Ok {
let mut map: HashMap<String, RedisString> =
HashMap::with_capacity(LEN);
{
let key_ctx = unsafe { $key.get_ctx() };
let mut ivalues = values.iter_mut();
unsafe {
$(
if let Some(p) = ivalues.next().unwrap().as_mut() {
map.insert($field.to_string(), RedisString::new(key_ctx, p));
}
)*
}
}
Ok(map)
} else {
Err(RedisError::Str("ERR key is not a hash value"))
}
} else {
use redis_module::RedisString;
Ok(HashMap::<String, RedisString>::with_capacity(0))
}
};
}