11use std:: {
22 ffi:: c_void,
3- ptr:: { self , addr_of_mut } ,
3+ ptr:: { self } ,
44} ;
55
6- use crate :: { key:: RedisKey , raw, Context , RedisString } ;
6+ use crate :: { key:: RedisKey , raw, RedisString } ;
77
88/// A cursor to scan field/value pairs of a (hash) key.
99///
1010/// This is a wrapper around the [RedisModule_ScanKey](https://redis.io/docs/latest/develop/reference/modules/modules-api-ref/#redismodule_scankey)
11- /// function from the C API. It provides access via a closure given to [`ScanKeyCursor::foreach`] and alternatively
12- /// provides a Rust iterator via [`ScanKeyCursor::iter`].
11+ /// function from the C API. It provides access via a closure given to [`ScanKeyCursor::foreach`]
1312///
14- /// Use `foreach` if the operation requires no copies and high performance. Use the iterator variant if you need to collect the results and/or
15- /// want to have access to the Rust iterator API.
16- ///
1713/// ## Example usage
1814///
19- /// Here we show how to extract values to communicate them back to the Redis client. We assume that the following hash key is setup:
15+ /// 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 :
2016///
2117/// ```text
2218/// HSET user:123 name Alice age 29 location Austin
2319/// ```
2420///
25- /// For using the `foreach` method:
21+ /// The following example command implementation scans all fields and values in the hash key and returns them as an array of RedisString.
2622///
2723/// ```ignore
2824/// fn example_scan_key_foreach(ctx: &Context) -> RedisResult {
@@ -40,22 +36,7 @@ use crate::{key::RedisKey, raw, Context, RedisString};
4036/// }
4137/// ```
4238///
43- /// For using the `iter` method:
44- ///
45- /// ```ignore
46- /// fn example_scan_key_foreach(ctx: &Context) -> RedisResult {
47- /// let mut res = Vec::new();
48- /// let key = ctx.open_key_with_flags("user:123", KeyFlags::NOEFFECTS | KeyFlags::NOEXPIRE | KeyFlags::ACCESS_EXPIRED );
49- /// let cursor = ScanKeyCursor::new(key);
50- /// for (field, value) in cursor.iter().enumerate() {
51- /// res.push(RedisValue::BulkRedisString(field));
52- /// res.push(RedisValue::BulkRedisString(value));
53- /// }
54- /// Ok(RedisValue::Array(res))
55- /// }
56- /// ```
57- ///
58- /// Both methods will produce the following output:
39+ /// The method will produce the following output:
5940///
6041/// ```text
6142/// 1) "name"
@@ -100,19 +81,6 @@ impl ScanKeyCursor {
10081 }
10182 }
10283 }
103-
104- /// Returns an iterator over all fields and values in the hash key.
105- ///
106- /// As the rust loop scope is detached from the C callback
107- /// we need to buffer the field/value pairs. That has performance implications. They are lower if the field/value pairs are
108- /// copied anyway, but even in that case not as fast as using the [`ScanKeyCursor::foreach`] method.
109- pub fn iter ( & self ) -> ScanKeyCursorIterator < ' _ > {
110- ScanKeyCursorIterator {
111- cursor : self ,
112- buf : Vec :: new ( ) ,
113- last_call : false ,
114- }
115- }
11684}
11785
11886impl Drop for ScanKeyCursor {
@@ -121,91 +89,6 @@ impl Drop for ScanKeyCursor {
12189 }
12290}
12391
124- pub type ScanKeyIteratorItem = ( RedisString , RedisString ) ;
125-
126- /// An iterator (state) over the field/value pairs of a hash key.
127- pub struct ScanKeyCursorIterator < ' a > {
128- /// The cursor that is used for the iteration
129- cursor : & ' a ScanKeyCursor ,
130-
131- // todo: use a vector with stack allocation for better performance
132- /// Buffer to hold the uninitialized data if the C callback is called multiple times.
133- buf : Vec < ScanKeyIteratorItem > ,
134-
135- /// Stores a flag that indicates if scan_key needs to be called again
136- last_call : bool ,
137- }
138-
139- /// The state machine for the iterator
140- enum IteratorState {
141- NeedToCallScanKey ,
142- HasBufferedItems ,
143- Done ,
144- }
145-
146- /// A stack slot that is used to pass data from the C callback to the iterator.
147- ///
148- /// It is mainly used to access the context and to store the buffered items.
149- struct StackSlot < ' a > {
150- ctx : Context ,
151- buf : & ' a mut Vec < ScanKeyIteratorItem > ,
152- }
153-
154- impl ScanKeyCursorIterator < ' _ > {
155- fn current_state ( & self ) -> IteratorState {
156- if !self . buf . is_empty ( ) {
157- IteratorState :: HasBufferedItems
158- } else if self . last_call {
159- IteratorState :: Done
160- } else {
161- IteratorState :: NeedToCallScanKey
162- }
163- }
164-
165- fn next_scan_call ( & mut self ) -> Option < ScanKeyIteratorItem > {
166- let ctx_ptr = self . cursor . key . ctx ;
167-
168- let mut stack_slot = StackSlot {
169- ctx : Context :: new ( ctx_ptr) ,
170- buf : & mut self . buf ,
171- } ;
172-
173- let data_ptr = addr_of_mut ! ( stack_slot) . cast :: < c_void > ( ) ;
174-
175- // Safety: Assumption: c-side initialized the function ptr and it is is never changed,
176- // i.e. after module initialization the function pointers stay valid till the end of the program.
177- let scan_key = unsafe { raw:: RedisModule_ScanKey . unwrap ( ) } ;
178-
179- // Safety: All pointers we pass here are guaranteed to remain valid during the `scan_key` call.
180- let ret = unsafe {
181- scan_key (
182- self . cursor . key . key_inner ,
183- self . cursor . inner_cursor ,
184- Some ( iterator_callback) ,
185- data_ptr,
186- )
187- } ;
188-
189- // Check if more calls are needed
190- if ret == 0 {
191- self . last_call = true ;
192- // we may still have buffered items
193- }
194-
195- if stack_slot. buf . is_empty ( ) {
196- // no items were returned, try again
197- None
198- } else {
199- self . next_buffered_item ( )
200- }
201- }
202-
203- fn next_buffered_item ( & mut self ) -> Option < ScanKeyIteratorItem > {
204- // todo: use different datatype / access pattern for performance
205- Some ( self . buf . remove ( 0 ) )
206- }
207- }
208-
20992/// The callback that is used by [`ScanKeyCursor::foreach`] as argument to `RedisModule_ScanKey`.
21093///
21194/// The `data` pointer is the closure given to [`ScanKeyCursor::foreach`] and the callback forwards
@@ -231,50 +114,3 @@ unsafe extern "C" fn foreach_callback<F: FnMut(&RedisKey, &RedisString, &RedisSt
231114
232115 key. take ( ) ; // we're not the owner of the key either
233116}
234-
235- /// The callback that is used inside the iterator variant accessible via [`ScanKeyCursor::iter`] for `RedisModule_ScanKey`.
236- ///
237- /// It buffers copies of the field and value strings as the lifetime of them ends when with the call
238- /// to `RedisModule_ScanKey` going out of scope.
239- ///
240- /// The `data` pointer is a stack slot of type [StackSlot] that is used to pass the data back to the iterator.
241- unsafe extern "C" fn iterator_callback (
242- _key : * mut raw:: RedisModuleKey ,
243- field : * mut raw:: RedisModuleString ,
244- value : * mut raw:: RedisModuleString ,
245- data : * mut c_void ,
246- ) {
247- // `data` is a stack slot
248- let slot = data. cast :: < StackSlot > ( ) ;
249- let slot = & mut ( * slot) ;
250-
251- // todo: use new-type with refcount handling for better performance, otherwise we have to copy at this point
252- // we know that this callback will be called in a loop scope and that we
253- // need to store the RedisString longer than the ScanKey invocation but not much
254- // longer and in case of batched results we don't need to store everything in memory
255- // but only the last batch.
256- let field = raw:: RedisModule_CreateStringFromString . unwrap ( ) ( slot. ctx . get_raw ( ) , field) ;
257- let value = raw:: RedisModule_CreateStringFromString . unwrap ( ) ( slot. ctx . get_raw ( ) , value) ;
258-
259- let field = RedisString :: from_redis_module_string ( slot. ctx . get_raw ( ) , field) ;
260- let value = RedisString :: from_redis_module_string ( slot. ctx . get_raw ( ) , value) ;
261-
262- slot. buf . push ( ( field, value) ) ;
263- }
264-
265- // Implements an iterator for `KeysCursor` that yields (RedisString, RedisString) in a Rust for loop.
266- // This is a wrapper around the RedisModule_ScanKey function from the C API and uses a pattern to get the values from the callback that
267- // is also used in stack unwinding scenarios. There is not common term for that but here we can think of it as a "stack slot" pattern.
268- impl Iterator for ScanKeyCursorIterator < ' _ > {
269- type Item = ScanKeyIteratorItem ;
270-
271- fn next ( & mut self ) -> Option < Self :: Item > {
272- let ctx = Context :: new ( self . cursor . key . ctx ) ;
273- ctx. log_notice ( "ScanKeyCursorIterator next() called" ) ;
274- match self . current_state ( ) {
275- IteratorState :: NeedToCallScanKey => self . next_scan_call ( ) ,
276- IteratorState :: HasBufferedItems => self . next_buffered_item ( ) ,
277- IteratorState :: Done => None ,
278- }
279- }
280- }
0 commit comments