Skip to content

Commit

Permalink
Added keys scan API. (#265)
Browse files Browse the repository at this point in the history
* Added keys scan API.

* Added comment on taken the underline RedisModuleKey.

* Apply suggestions from code review

Co-authored-by: Guy Korland <[email protected]>

Co-authored-by: Guy Korland <[email protected]>
  • Loading branch information
MeirShpilraien and gkorland authored Jan 25, 2023
1 parent 2587a7a commit dbc7883
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 1 deletion.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ name = "info"
crate-type = ["cdylib"]
required-features = []

[[example]]
name = "scan_keys"
crate-type = ["cdylib"]

[dependencies]
bitflags = "1.2"
libc = "0.2"
Expand Down
24 changes: 24 additions & 0 deletions examples/scan_keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#[macro_use]
extern crate redis_module;

use redis_module::{Context, KeysCursor, RedisResult, RedisString, RedisValue};

fn scan_keys(ctx: &Context, _args: Vec<RedisString>) -> RedisResult {
let cursor = KeysCursor::new();
let mut res = Vec::new();
while cursor.scan(ctx, &|_ctx, key_name, _key| {
res.push(RedisValue::BulkRedisString(key_name));
}) {}
Ok(RedisValue::Array(res))
}

//////////////////////////////////////////////////////

redis_module! {
name: "scan",
version: 1,
data_types: [],
commands: [
["scan_keys", scan_keys, "readonly", 0, 0, 0],
],
}
68 changes: 68 additions & 0 deletions src/context/keys_cursor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use crate::context::Context;
use crate::key::RedisKey;
use crate::raw;
use crate::redismodule::RedisString;
use std::ffi::c_void;

pub struct KeysCursor {
inner_cursor: *mut raw::RedisModuleScanCursor,
}

extern "C" fn scan_callback<C: FnMut(&Context, RedisString, Option<&RedisKey>)>(
ctx: *mut raw::RedisModuleCtx,
key_name: *mut raw::RedisModuleString,
key: *mut raw::RedisModuleKey,
private_data: *mut ::std::os::raw::c_void,
) {
let context = Context::new(ctx);
let key_name = RedisString::new(ctx, key_name);
let redis_key = if key.is_null() {
None
} else {
Some(RedisKey::from_raw_parts(ctx, key))
};
let callback = unsafe { &mut *(private_data.cast::<C>()) };
callback(&context, key_name, redis_key.as_ref());

// we are not the owner of the key, so we must take the underline *mut raw::RedisModuleKey so it will not be freed.
redis_key.map(|v| v.take());
}

impl KeysCursor {
pub fn new() -> KeysCursor {
let inner_cursor = unsafe { raw::RedisModule_ScanCursorCreate.unwrap()() };
KeysCursor { inner_cursor }
}

pub fn scan<F: FnMut(&Context, RedisString, Option<&RedisKey>)>(
&self,
ctx: &Context,
callback: &F,
) -> bool {
let res = unsafe {
raw::RedisModule_Scan.unwrap()(
ctx.ctx,
self.inner_cursor,
Some(scan_callback::<F>),
callback as *const F as *mut c_void,
)
};
res != 0
}

pub fn restart(&self) {
unsafe { raw::RedisModule_ScanCursorRestart.unwrap()(self.inner_cursor) };
}
}

impl Default for KeysCursor {
fn default() -> Self {
Self::new()
}
}

impl Drop for KeysCursor {
fn drop(&mut self) {
unsafe { raw::RedisModule_ScanCursorDestroy.unwrap()(self.inner_cursor) };
}
}
2 changes: 2 additions & 0 deletions src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub mod blocked;

pub mod info;

pub mod keys_cursor;

/// `Context` is a structure that's designed to give us a high-level interface to
/// the Redis module API by abstracting away the raw C FFI calls.
pub struct Context {
Expand Down
17 changes: 16 additions & 1 deletion src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,24 @@ pub struct RedisKey {
}

impl RedisKey {
pub(crate) fn take(mut self) -> *mut raw::RedisModuleKey {
let res = self.key_inner;
self.key_inner = std::ptr::null_mut();
res
}

pub fn open(ctx: *mut raw::RedisModuleCtx, key: &RedisString) -> Self {
let key_inner = raw::open_key(ctx, key.inner, to_raw_mode(KeyMode::Read));
Self { ctx, key_inner }
}

pub(crate) fn from_raw_parts(
ctx: *mut raw::RedisModuleCtx,
key_inner: *mut raw::RedisModuleKey,
) -> Self {
Self { ctx, key_inner }
}

/// # Panics
///
/// Will panic if `RedisModule_ModuleTypeGetValue` is missing in redismodule.h
Expand Down Expand Up @@ -128,7 +141,9 @@ impl RedisKey {
impl Drop for RedisKey {
// Frees resources appropriately as a RedisKey goes out of scope.
fn drop(&mut self) {
raw::close_key(self.key_inner);
if !self.key_inner.is_null() {
raw::close_key(self.key_inner);
}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub use crate::context::thread_safe::{DetachedFromClient, ThreadSafeContext};
#[cfg(feature = "experimental-api")]
pub use crate::raw::NotifyEvent;

pub use crate::context::keys_cursor::KeysCursor;
pub use crate::context::Context;
pub use crate::raw::*;
pub use crate::redismodule::*;
Expand Down
26 changes: 26 additions & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,29 @@ fn test_string() -> Result<()> {

Ok(())
}

#[test]
fn test_scan() -> Result<()> {
let port: u16 = 6486;
let _guards = vec![start_redis_server_with_module("scan_keys", port)
.with_context(|| "failed to start redis server")?];
let mut con =
get_redis_connection(port).with_context(|| "failed to connect to redis server")?;

redis::cmd("set")
.arg(&["x", "1"])
.query(&mut con)
.with_context(|| "failed to run string.set")?;

redis::cmd("set")
.arg(&["y", "1"])
.query(&mut con)
.with_context(|| "failed to run string.set")?;

let mut res: Vec<String> = redis::cmd("scan_keys").query(&mut con)?;
res.sort();

assert_eq!(&res, &["x", "y"]);

Ok(())
}

0 comments on commit dbc7883

Please sign in to comment.