Skip to content

Commit 90f514c

Browse files
committed
remove iterator method
1 parent a8c1670 commit 90f514c

File tree

3 files changed

+8
-216
lines changed

3 files changed

+8
-216
lines changed

examples/scan_keys.rs

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,7 @@
33
// The example implements three commands:
44
//
55
// 1. `scan_keys` - scans all keys in the database and returns their names as an array of RedisString.
6-
// 2. `scan_key_it <key>` - scans all fields and values in a hash key providing an iterator and return the field/value pairs as an array of RedisString.
7-
// 3. `scan_key_fe <key>` - scans all fields and values in a hash key using a closure that stores tthe field/value pairs as an array of RedisString.
8-
//
9-
// `scan_key_it` always copies the field and value strings, while `scan_key_fe` uses references to the field and value strings. In that example
10-
// both implementations need to clone the strings, because we want to return them as an array of RedisString.
11-
//
6+
// 2. `scan_key <key>` - scans all fields and values in a hash key using a closure that stores the field/value pairs as an array of RedisString.
127

138
use redis_module::{
149
key::{KeyFlags, RedisKey}, redis_module, Context, KeysCursor, RedisError, RedisResult, RedisString, RedisValue, ScanKeyCursor
@@ -50,26 +45,6 @@ fn scan_key_foreach(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
5045
Ok(RedisValue::Array(res))
5146
}
5247

53-
/// Scans all fields and values in a hash key and returns them as an array of RedisString.
54-
/// The command takes one argument: the name of the hash key to scan.
55-
fn scan_key_iterator(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
56-
// only argument is the key name
57-
if args.len() != 2 {
58-
return Err(RedisError::WrongArity);
59-
}
60-
61-
let key_name = &args[1];
62-
let mut res = Vec::new();
63-
let key = ctx.open_key_with_flags(key_name, KeyFlags::NOEFFECTS | KeyFlags::NOEXPIRE | KeyFlags::ACCESS_EXPIRED );
64-
let cursor = ScanKeyCursor::new(key);
65-
66-
for (no, (field, value)) in cursor.iter().enumerate() {
67-
res.push(RedisValue::BulkRedisString(field));
68-
res.push(RedisValue::BulkRedisString(value));
69-
}
70-
71-
Ok(RedisValue::Array(res))
72-
}
7348

7449
//////////////////////////////////////////////////////
7550

@@ -80,7 +55,6 @@ redis_module! {
8055
data_types: [],
8156
commands: [
8257
["scan_keys", scan_keys, "readonly", 0, 0, 0, ""],
83-
["scan_key_it", scan_key_iterator, "readonly", 0, 0, 0, ""],
84-
["scan_key_fe", scan_key_foreach, "readonly", 0, 0, 0, ""],
58+
["scan_key", scan_key_foreach, "readonly", 0, 0, 0, ""],
8559
],
8660
}

src/context/key_cursor.rs

Lines changed: 6 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
11
use 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

11886
impl 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-
}

tests/integration.rs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -200,24 +200,6 @@ fn test_scan() -> Result<()> {
200200
Ok(())
201201
}
202202

203-
#[test]
204-
fn test_scan_key_it() -> Result<()> {
205-
let mut con = TestConnection::new("scan_keys");
206-
207-
redis::cmd("hset")
208-
.arg(&[
209-
"user:123", "name", "Alice", "age", "29", "location", "Austin",
210-
])
211-
.query::<()>(&mut con)
212-
.with_context(|| "failed to hset")?;
213-
214-
let res: Vec<String> = redis::cmd("scan_key_it")
215-
.arg(&["user:123"])
216-
.query(&mut con)?;
217-
assert_eq!(&res, &["name", "Alice", "age", "29", "location", "Austin"]);
218-
Ok(())
219-
}
220-
221203
#[test]
222204
fn test_scan_key_foreach() -> Result<()> {
223205
let mut con = TestConnection::new("scan_keys");

0 commit comments

Comments
 (0)