Skip to main content

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}