Skip to content

Commit 85da232

Browse files
authored
Add infrastructure for exposing hash-multi-get functionality (#105)
This commit adds a `hash_get_multi` function in the `raw` module, and a private `hash_mget_key` function in the `key` module. In the interests of avoiding code duplication, the corresponding single-get functions are removed, and the public RedisKey*.hash_get functionality is implemented via multi-get. Public-facing RedisKey* methods for hash multi-get should be the subject of their own commit.
1 parent 20038e7 commit 85da232

File tree

2 files changed

+113
-22
lines changed

2 files changed

+113
-22
lines changed

src/key.rs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ impl RedisKey {
8585
let val = if self.is_null() {
8686
None
8787
} else {
88-
hash_get_key(self.ctx, self.key_inner, field)
88+
hash_mget_key(self.ctx, self.key_inner, &[field])?
89+
.pop()
90+
.expect("hash_mget_key should return vector of same length as input")
8991
};
9092
Ok(val)
9193
}
@@ -150,7 +152,9 @@ impl RedisKeyWritable {
150152
}
151153

152154
pub fn hash_get(&self, field: &str) -> Result<Option<RedisString>, RedisError> {
153-
Ok(hash_get_key(self.ctx, self.key_inner, field))
155+
Ok(hash_mget_key(self.ctx, self.key_inner, &[field])?
156+
.pop()
157+
.expect("hash_mget_key should return vector of same length as input"))
154158
}
155159

156160
pub fn set_expire(&self, expire: Duration) -> RedisResult {
@@ -246,17 +250,34 @@ fn read_key(key: *mut raw::RedisModuleKey) -> Result<String, Utf8Error> {
246250
)
247251
}
248252

249-
fn hash_get_key(
253+
/// Get an arbitrary number of hash fields from a key by batching calls
254+
/// to `raw::hash_get_multi`.
255+
pub fn hash_mget_key<T>(
250256
ctx: *mut raw::RedisModuleCtx,
251257
key: *mut raw::RedisModuleKey,
252-
field: &str,
253-
) -> Option<RedisString> {
254-
let res = raw::hash_get(key, field);
255-
if res.is_null() {
256-
None
257-
} else {
258-
Some(RedisString::new(ctx, res))
258+
fields: &[T],
259+
) -> Result<Vec<Option<RedisString>>, RedisError>
260+
where
261+
T: Into<Vec<u8>> + Clone,
262+
{
263+
const BATCH_SIZE: usize = 12;
264+
265+
let mut values = Vec::with_capacity(fields.len());
266+
let mut values_raw = [std::ptr::null_mut(); BATCH_SIZE];
267+
268+
for chunk_fields in fields.chunks(BATCH_SIZE) {
269+
let mut chunk_values = &mut values_raw[..chunk_fields.len()];
270+
raw::hash_get_multi(key, chunk_fields, &mut chunk_values)?;
271+
values.extend(chunk_values.iter().map(|ptr| {
272+
if ptr.is_null() {
273+
None
274+
} else {
275+
Some(RedisString::new(ctx, *ptr))
276+
}
277+
}));
259278
}
279+
280+
Ok(values)
260281
}
261282

262283
fn to_raw_mode(mode: KeyMode) -> raw::KeyMode {

src/raw.rs

Lines changed: 82 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ use std::ptr;
1515
use std::slice;
1616

1717
pub use crate::redisraw::bindings::*;
18-
use crate::RedisBuffer;
1918
use crate::RedisString;
19+
use crate::{RedisBuffer, RedisError};
2020

2121
bitflags! {
2222
pub struct KeyMode: c_int {
@@ -232,18 +232,88 @@ pub fn string_dma(key: *mut RedisModuleKey, len: *mut size_t, mode: KeyMode) ->
232232
unsafe { RedisModule_StringDMA.unwrap()(key, len, mode.bits) }
233233
}
234234

235-
pub fn hash_get(key: *mut RedisModuleKey, field: &str) -> *mut RedisModuleString {
236-
let res: *mut RedisModuleString = ptr::null_mut();
237-
unsafe {
238-
RedisModule_HashGet.unwrap()(
239-
key,
240-
REDISMODULE_HASH_CFIELDS as i32,
241-
CString::new(field).unwrap().as_ptr(),
242-
&res,
243-
ptr::null::<c_char>(),
244-
);
235+
pub fn hash_get_multi<T>(
236+
key: *mut RedisModuleKey,
237+
fields: &[T],
238+
values: &mut [*mut RedisModuleString],
239+
) -> Result<(), RedisError>
240+
where
241+
T: Into<Vec<u8>> + Clone,
242+
{
243+
assert_eq!(fields.len(), values.len());
244+
245+
let mut fi = fields.iter();
246+
let mut vi = values.iter_mut();
247+
248+
macro_rules! rm {
249+
() => { unsafe {
250+
RedisModule_HashGet.unwrap()(key, REDISMODULE_HASH_CFIELDS as i32,
251+
ptr::null::<c_char>())
252+
}};
253+
($($args:expr)*) => { unsafe {
254+
RedisModule_HashGet.unwrap()(
255+
key, REDISMODULE_HASH_CFIELDS as i32,
256+
$($args),*,
257+
ptr::null::<c_char>()
258+
)
259+
}};
260+
}
261+
macro_rules! f {
262+
() => {
263+
CString::new((*fi.next().unwrap()).clone())
264+
.unwrap()
265+
.as_ptr()
266+
};
267+
}
268+
macro_rules! v {
269+
() => {
270+
vi.next().unwrap()
271+
};
272+
}
273+
274+
// This convoluted code is necessary since Redis only exposes a varargs API for HashGet
275+
// to modules. Unfortunately there's no straightforward or portable way of calling a
276+
// a varargs function with a variable number of arguments that is determined at runtime.
277+
// See also the following Redis ticket: https://github.com/redis/redis/issues/7860
278+
let res = Status::from(match fields.len() {
279+
0 => rm! {},
280+
1 => rm! {f!() v!()},
281+
2 => rm! {f!() v!() f!() v!()},
282+
3 => rm! {f!() v!() f!() v!() f!() v!()},
283+
4 => rm! {f!() v!() f!() v!() f!() v!() f!() v!()},
284+
5 => rm! {f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()},
285+
6 => rm! {f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()},
286+
7 => rm! {
287+
f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
288+
f!() v!()
289+
},
290+
8 => rm! {
291+
f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
292+
f!() v!() f!() v!()
293+
},
294+
9 => rm! {
295+
f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
296+
f!() v!() f!() v!() f!() v!()
297+
},
298+
10 => rm! {
299+
f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
300+
f!() v!() f!() v!() f!() v!() f!() v!()
301+
},
302+
11 => rm! {
303+
f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
304+
f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
305+
},
306+
12 => rm! {
307+
f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
308+
f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
309+
},
310+
_ => panic!("Unsupported length"),
311+
});
312+
313+
match res {
314+
Status::Ok => Ok(()),
315+
_ => Err(RedisError::Str("ERR key is not a hash value")),
245316
}
246-
res
247317
}
248318

249319
pub fn hash_set(key: *mut RedisModuleKey, field: &str, value: *mut RedisModuleString) -> Status {

0 commit comments

Comments
 (0)