redis_module/context/key_cursor.rs
1use std::{
2 ffi::c_void,
3 mem,
4 ptr::{self},
5};
6
7use crate::{key::RedisKey, raw, RedisString};
8
9/// A cursor to scan field/value pairs of a (hash) key.
10///
11/// It provides access via a closure given to [`ScanKeyCursor::for_each`] or if you need more control, you can use [`ScanKeyCursor::scan`]
12/// and implement your own loop, e.g. to allow an early stop.
13///
14/// ## Example usage
15///
16/// Here we show how to extract values to communicate them back to the Redis client. We assume that the following hash key is setup before:
17///
18/// ```text
19/// HSET user:123 name Alice age 29 location Austin
20/// ```
21///
22/// The following example command implementation scans all fields and values in the hash key and returns them as an array of RedisString.
23///
24/// ```ignore
25/// fn example_scan_key_for_each(ctx: &Context) -> RedisResult {
26/// let key = ctx.open_key_with_flags("user:123", KeyFlags::NOEFFECTS | KeyFlags::NOEXPIRE | KeyFlags::ACCESS_EXPIRED );
27/// let cursor = ScanKeyCursor::new(key);
28///
29/// let res = RefCell::new(Vec::new());
30/// cursor.for_each(|_key, field, value| {
31/// let mut res = res.borrow_mut();
32/// res.push(RedisValue::BulkRedisString(field.clone()));
33/// res.push(RedisValue::BulkRedisString(value.clone()));
34/// });
35///
36/// Ok(RedisValue::Array(res.take()))
37/// }
38/// ```
39///
40/// The method will produce the following output:
41///
42/// ```text
43/// 1) "name"
44/// 2) "Alice"
45/// 3) "age"
46/// 4) "29"
47/// 5) "location"
48/// 6) "Austin"
49/// ```
50pub struct ScanKeyCursor {
51 key: RedisKey,
52 inner_cursor: *mut raw::RedisModuleScanCursor,
53}
54
55impl ScanKeyCursor {
56 /// Creates a new scan cursor for the given key.
57 pub fn new(key: RedisKey) -> Self {
58 let inner_cursor = unsafe { raw::RedisModule_ScanCursorCreate.unwrap()() };
59 Self { key, inner_cursor }
60 }
61
62 /// Restarts the cursor from the beginning.
63 pub fn restart(&self) {
64 unsafe { raw::RedisModule_ScanCursorRestart.unwrap()(self.inner_cursor) };
65 }
66
67 /// Implements a call to `RedisModule_ScanKey` and calls the given closure for each callback invocation by ScanKey.
68 /// Returns `true` if there are more fields to scan, `false` otherwise.
69 ///
70 /// The callback may be called multiple times per `RedisModule_ScanKey` invocation.
71 ///
72 /// ## Example
73 ///
74 /// ```ignore
75 /// while cursor.scan(|_key, field, value| {
76 /// // do something with field and value
77 /// }) {
78 /// // do something between scans if needed, like an early stop
79 /// }
80 pub fn scan<F: FnMut(&RedisKey, &RedisString, &RedisString)>(&self, f: F) -> bool {
81 unsafe extern "C" fn scan_callback<F: FnMut(&RedisKey, &RedisString, &RedisString)>(
82 key: *mut raw::RedisModuleKey,
83 field: *mut raw::RedisModuleString,
84 value: *mut raw::RedisModuleString,
85 data: *mut c_void,
86 ) {
87 let ctx = ptr::null_mut();
88 let key = RedisKey::from_raw_parts(ctx, key);
89
90 let field = RedisString::from_redis_module_string(ctx, field);
91 let value = RedisString::from_redis_module_string(ctx, value);
92
93 let callback = unsafe { &mut *(data.cast::<F>()) };
94 callback(&key, &field, &value);
95
96 // We don't own any of the passed in pointers, so we must ensure we don't run their destructors here
97 mem::forget(field);
98 mem::forget(value);
99 mem::forget(key);
100 }
101
102 // Safety: The c-side initialized the function ptr and it is is never changed,
103 // i.e. after module initialization the function pointers stay valid till the end of the program.
104 let scan_key = unsafe { raw::RedisModule_ScanKey.unwrap() };
105
106 let res = unsafe {
107 scan_key(
108 self.key.key_inner,
109 self.inner_cursor,
110 Some(scan_callback::<F>),
111 &f as *const F as *mut c_void,
112 )
113 };
114
115 res != 0
116 }
117
118 /// Implements a callback based for_each loop over all fields and values in the hash key.
119 /// If you need more control, e.g. stopping after a scan invocation, then use [`ScanKeyCursor::scan`] directly.
120 pub fn for_each<F: FnMut(&RedisKey, &RedisString, &RedisString)>(&self, mut f: F) {
121 while self.scan(&mut f) {
122 // do nothing, the callback does the work
123 }
124 }
125}
126
127impl Drop for ScanKeyCursor {
128 fn drop(&mut self) {
129 unsafe { raw::RedisModule_ScanCursorDestroy.unwrap()(self.inner_cursor) };
130 }
131}