@@ -8,22 +8,23 @@ use crate::{key::RedisKey, raw, Context, RedisString};
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 [`ScanKeyCursor::foreach`] and provides a Rust iterator via [`ScanKeyCursor::iter`].
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`].
1213///
13- /// Use the former if the operation can be performed in the callback, as it is more efficient . Use the latter if you need to collect the results and/or
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
1415/// want to have access to the Rust iterator API.
1516///
1617/// ## Example usage
1718///
1819/// Here we show how to extract values to communicate them back to the Redis client. We assume that the following hash key is setup:
1920///
20- /// ```no_run
21+ /// ```text
2122/// HSET user:123 name Alice age 29 location Austin
2223/// ```
2324///
2425/// For using the `foreach` method:
2526///
26- /// ```no_run
27+ /// ```ignore
2728/// fn example_scan_key_foreach(ctx: &Context) -> RedisResult {
2829/// let key = ctx.open_key_with_flags("user:123", KeyFlags::NOEFFECTS | KeyFlags::NOEXPIRE | KeyFlags::ACCESS_EXPIRED );
2930/// let cursor = ScanKeyCursor::new(key);
@@ -41,7 +42,7 @@ use crate::{key::RedisKey, raw, Context, RedisString};
4142///
4243/// For using the `iter` method:
4344///
44- /// ```no_run
45+ /// ```ignore
4546/// fn example_scan_key_foreach(ctx: &Context) -> RedisResult {
4647/// let mut res = Vec::new();
4748/// let key = ctx.open_key_with_flags("user:123", KeyFlags::NOEFFECTS | KeyFlags::NOEXPIRE | KeyFlags::ACCESS_EXPIRED );
@@ -70,16 +71,18 @@ pub struct ScanKeyCursor {
7071}
7172
7273impl ScanKeyCursor {
74+ /// Creates a new scan cursor for the given key.
7375 pub fn new ( key : RedisKey ) -> Self {
7476 let inner_cursor = unsafe { raw:: RedisModule_ScanCursorCreate . unwrap ( ) ( ) } ;
7577 Self { key, inner_cursor }
7678 }
7779
80+ /// Restarts the cursor from the beginning.
7881 pub fn restart ( & self ) {
7982 unsafe { raw:: RedisModule_ScanCursorRestart . unwrap ( ) ( self . inner_cursor ) } ;
8083 }
8184
82- /// Implements a callback based foreach loop over all fields and values in the hash key, use for optimal performance.
85+ /// Implements a callback based foreach loop over all fields and values in the hash key, use that for optimal performance.
8386 pub fn foreach < F : Fn ( & RedisKey , & RedisString , & RedisString ) > ( & self , f : F ) {
8487 // Safety: Assumption: c-side initialized the function ptr and it is is never changed,
8588 // i.e. after module initialization the function pointers stay valid till the end of the program.
@@ -98,9 +101,12 @@ impl ScanKeyCursor {
98101 }
99102 }
100103
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.
101109 pub fn iter ( & self ) -> ScanKeyCursorIterator < ' _ > {
102- let ctx = Context :: new ( self . key . ctx ) ;
103- ctx. log_notice ( "Starting ScanKeyCursor iteration" ) ;
104110 ScanKeyCursorIterator {
105111 cursor : self ,
106112 buf : Vec :: new ( ) ,
@@ -117,6 +123,7 @@ impl Drop for ScanKeyCursor {
117123
118124pub type ScanKeyIteratorItem = ( RedisString , RedisString ) ;
119125
126+ /// An iterator (state) over the field/value pairs of a hash key.
120127pub struct ScanKeyCursorIterator < ' a > {
121128 /// The cursor that is used for the iteration
122129 cursor : & ' a ScanKeyCursor ,
@@ -199,9 +206,10 @@ impl ScanKeyCursorIterator<'_> {
199206 }
200207}
201208
202- /// The callback that is called by `RedisModule_ScanKey` to return the field and value strings .
209+ /// The callback that is used by [`ScanKeyCursor::foreach`] as argument to `RedisModule_ScanKey` .
203210///
204- /// The `data` pointer is a stack slot of type `RawData` that is used to pass the data back to the iterator.
211+ /// The `data` pointer is the closure given to [`ScanKeyCursor::foreach`] and the callback forwards
212+ /// references to the key, field and value to that closure.
205213unsafe extern "C" fn foreach_callback < F : Fn ( & RedisKey , & RedisString , & RedisString ) > (
206214 key : * mut raw:: RedisModuleKey ,
207215 field : * mut raw:: RedisModuleString ,
@@ -224,43 +232,37 @@ unsafe extern "C" fn foreach_callback<F: Fn(&RedisKey, &RedisString, &RedisStrin
224232 key. take ( ) ; // we're not the owner of the key either
225233}
226234
227- /// The callback that is called by `RedisModule_ScanKey` to return the field and value strings.
228- ///
229- /// The `data` pointer is a stack slot of type `RawData` that is used to pass the data back to the iterator.
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.
230241unsafe extern "C" fn iterator_callback (
231242 _key : * mut raw:: RedisModuleKey ,
232243 field : * mut raw:: RedisModuleString ,
233244 value : * mut raw:: RedisModuleString ,
234245 data : * mut c_void ,
235246) {
236- // SAFETY: this is the responsibility of the caller, see only usage below in `next()`
237- // `data` is a stack slot of type Data
247+ // `data` is a stack slot
238248 let slot = data. cast :: < StackSlot > ( ) ;
239249 let slot = & mut ( * slot) ;
240250
241- // todo: use new-type with refcount handling for better performance
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.
242256 let field = raw:: RedisModule_CreateStringFromString . unwrap ( ) ( slot. ctx . get_raw ( ) , field) ;
243257 let value = raw:: RedisModule_CreateStringFromString . unwrap ( ) ( slot. ctx . get_raw ( ) , value) ;
244258
245259 let field = RedisString :: from_redis_module_string ( slot. ctx . get_raw ( ) , field) ;
246260 let value = RedisString :: from_redis_module_string ( slot. ctx . get_raw ( ) , value) ;
247261
248- if slot. buf . is_empty ( ) {
249- let out = format ! ( "CB - Value tu return - Field: {}, Value: {}" , field, value) ;
250- slot. ctx . log_notice ( & out) ;
251- } else {
252- // This is the case where the callback is called multiple times.
253- // We need to buffer the data in the iterator state.
254- let out = format ! (
255- "CB - Buffer for future use - Field: {}, Value: {}" ,
256- field, value
257- ) ;
258- slot. ctx . log_notice ( & out) ;
259- }
260262 slot. buf . push ( ( field, value) ) ;
261263}
262264
263- // Implements an iterator for `KeysCursor` that yields (RedisKey, *mut RedisModuleString, *mut RedisModuleString ) in a Rust for loop.
265+ // Implements an iterator for `KeysCursor` that yields (RedisString, RedisString ) in a Rust for loop.
264266// 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
265267// 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.
266268impl Iterator for ScanKeyCursorIterator < ' _ > {
0 commit comments