diff --git a/Cargo.toml b/Cargo.toml index abfbcd78..c60327a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,21 @@ name = "info" crate-type = ["cdylib"] required-features = [] +[[example]] +name = "stream" +crate-type = ["cdylib"] +required-features = ["experimental-api"] + +[[example]] +name = "server_events" +crate-type = ["cdylib"] +required-features = ["experimental-api"] + +[[example]] +name = "scan" +crate-type = ["cdylib"] +required-features = ["experimental-api"] + [dependencies] bitflags = "1.2" libc = "0.2" @@ -70,6 +85,7 @@ regex = "1" strum_macros = "0.24" #failure = "0.1" backtrace = "0.3" +nix = "0.26" [dev-dependencies] anyhow = "1.0.38" @@ -82,8 +98,11 @@ cc = "1.0" [features] default = [] experimental-api = [] - -# Workaround to allow cfg(feature = "test") in dependencies: -# https://github.com/rust-lang/rust/issues/59168#issuecomment-472653680 -# This requires running the tests with `--features test` -test = [] +# Allows to fallback to the default Rust allocator when the Redis +# allocator is not available. Particularly useful for tests without +# having a running Redis instance and running as a Redis module. +fallback_to_system_allocator = [] +# TODO remove this "feature" as its name is misleading and using of +# which should be avoided in favour of well-named and self-explanatory +# features doing just one job following the SRP and KISS principles. +test = ["fallback_to_system_allocator"] diff --git a/examples/data_type.rs b/examples/data_type.rs index 69b94ad4..a4680f16 100644 --- a/examples/data_type.rs +++ b/examples/data_type.rs @@ -33,11 +33,16 @@ static MY_REDIS_TYPE: RedisType = RedisType::new( unlink: None, copy: None, defrag: None, + + free_effort2: None, + unlink2: None, + copy2: None, + mem_usage2: None, }, ); unsafe extern "C" fn free(value: *mut c_void) { - Box::from_raw(value.cast::()); + drop(Box::from_raw(value.cast::())); } fn alloc_set(ctx: &Context, args: Vec) -> RedisResult { diff --git a/examples/events.rs b/examples/events.rs index 34608084..22dbce95 100644 --- a/examples/events.rs +++ b/examples/events.rs @@ -3,15 +3,17 @@ extern crate redis_module; use redis_module::{Context, NotifyEvent, RedisError, RedisResult, RedisString, Status}; -fn on_event(ctx: &Context, event_type: NotifyEvent, event: &str, key: &str) { +fn on_event(ctx: &Context, event_type: NotifyEvent, event: &str, key: &[u8]) { let msg = format!( "Received event: {:?} on key: {} via event: {}", - event_type, key, event + event_type, + std::str::from_utf8(key).unwrap(), + event ); ctx.log_debug(msg.as_str()); } -fn on_stream(ctx: &Context, _event_type: NotifyEvent, _event: &str, _key: &str) { +fn on_stream(ctx: &Context, _event_type: NotifyEvent, _event: &str, _key: &[u8]) { ctx.log_debug("Stream event received!"); } diff --git a/examples/scan.rs b/examples/scan.rs new file mode 100644 index 00000000..7d5408d4 --- /dev/null +++ b/examples/scan.rs @@ -0,0 +1,28 @@ +#[macro_use] +extern crate redis_module; + +use redis_module::{ + context::keys_cursor::KeysCursor, Context, RedisResult, RedisString, RedisValue, +}; + +fn scan_keys(ctx: &Context, _args: Vec) -> RedisResult { + let mut keys = Vec::new(); + let cursor = KeysCursor::new(); + while cursor.scan(ctx, &|_ctx, key_name, _key| { + keys.push(RedisValue::BulkString( + key_name.try_as_str().unwrap().to_string(), + )); + }) {} + Ok(keys.into()) +} + +////////////////////////////////////////////////////// + +redis_module! { + name: "scan", + version: 1, + data_types: [], + commands: [ + ["SCAN_KEYS", scan_keys, "fast deny-oom readonly", 0, 0, 0], + ], +} diff --git a/examples/server_events.rs b/examples/server_events.rs new file mode 100644 index 00000000..1f9030ae --- /dev/null +++ b/examples/server_events.rs @@ -0,0 +1,55 @@ +#[macro_use] +extern crate redis_module; + +use redis_module::{ + context::server_events::ServerEventData, Context, RedisResult, RedisString, RedisValue, +}; + +static mut NUM_FLUSHES: usize = 0; +static mut NUM_ROLED_CHANGED: usize = 0; +static mut NUM_LOADINGS: usize = 0; + +fn num_flushed(_ctx: &Context, _args: Vec) -> RedisResult { + Ok(RedisValue::Integer(unsafe { NUM_FLUSHES } as i64)) +} + +fn num_roled_changed(_ctx: &Context, _args: Vec) -> RedisResult { + Ok(RedisValue::Integer(unsafe { NUM_ROLED_CHANGED } as i64)) +} + +fn num_loading(_ctx: &Context, _args: Vec) -> RedisResult { + Ok(RedisValue::Integer(unsafe { NUM_LOADINGS } as i64)) +} + +fn on_role_changed(_ctx: &Context, _event_data: ServerEventData) { + let num_roled_changed = unsafe { &mut NUM_ROLED_CHANGED }; + *num_roled_changed = *num_roled_changed + 1; +} + +fn on_loading_event(_ctx: &Context, _event_data: ServerEventData) { + let num_loading = unsafe { &mut NUM_LOADINGS }; + *num_loading = *num_loading + 1; +} + +fn on_flush_event(_ctx: &Context, _event_data: ServerEventData) { + let num_flushed = unsafe { &mut NUM_FLUSHES }; + *num_flushed = *num_flushed + 1; +} + +////////////////////////////////////////////////////// + +redis_module! { + name: "server_events", + version: 1, + data_types: [], + commands: [ + ["NUM_FLUSHED", num_flushed, "fast deny-oom readonly", 0, 0, 0], + ["NUM_ROLED_CHANGED", num_roled_changed, "fast deny-oom readonly", 0, 0, 0], + ["NUM_LOADING", num_loading, "fast deny-oom readonly", 0, 0, 0], + ], + server_events: [ + [@RuleChanged: on_role_changed], + [@Loading: on_loading_event], + [@Flush: on_flush_event], + ] +} diff --git a/examples/stream.rs b/examples/stream.rs new file mode 100644 index 00000000..7c8fb35f --- /dev/null +++ b/examples/stream.rs @@ -0,0 +1,48 @@ +#[macro_use] +extern crate redis_module; + +use redis_module::raw::{KeyType, RedisModuleStreamID}; +use redis_module::{Context, NextArg, RedisError, RedisResult, RedisString, RedisValue}; + +fn stream_read_from(ctx: &Context, args: Vec) -> RedisResult { + let mut args = args.into_iter().skip(1); + + let stream_key = args.next_arg()?; + + let stream = ctx.open_key(&stream_key); + + let key_type = stream.key_type(); + + if key_type != KeyType::Stream { + return Err(RedisError::WrongType); + } + + let mut iter = stream.get_stream_iterator()?; + let element = iter.next(); + let id_to_keep = iter.next().as_ref().map_or_else( + || RedisModuleStreamID { + ms: u64::MAX, + seq: u64::MAX, + }, + |e| e.id, + ); + + let stream = ctx.open_key_writable(&stream_key); + stream.trim_stream_by_id(id_to_keep, false)?; + + Ok(match element { + Some(e) => RedisValue::BulkString(format!("{}-{}", e.id.ms, e.id.seq)), + None => RedisValue::Null, + }) +} + +////////////////////////////////////////////////////// + +redis_module! { + name: "stream", + version: 1, + data_types: [], + commands: [ + ["STREAM_POP", stream_read_from, "fast deny-oom", 1, 1, 1], + ], +} diff --git a/examples/test_helper.rs b/examples/test_helper.rs index e10271db..506bc45a 100644 --- a/examples/test_helper.rs +++ b/examples/test_helper.rs @@ -12,7 +12,6 @@ fn test_helper_version(ctx: &Context, _args: Vec) -> RedisResult { Ok(response.into()) } -#[cfg(feature = "test")] fn test_helper_version_rm_call(ctx: &Context, _args: Vec) -> RedisResult { let ver = ctx.get_redis_version_rm_call()?; let response: Vec = vec![ver.major.into(), ver.minor.into(), ver.patch.into()]; @@ -43,7 +42,6 @@ fn add_info(ctx: &InfoContext, _for_crash_report: bool) { ////////////////////////////////////////////////////// -#[cfg(feature = "test")] redis_module! { name: "test_helper", version: 1, diff --git a/src/alloc.rs b/src/alloc.rs index 1630ba3a..51e8dd37 100644 --- a/src/alloc.rs +++ b/src/alloc.rs @@ -1,29 +1,60 @@ use std::alloc::{GlobalAlloc, Layout}; -use std::os::raw::c_void; use crate::raw; +/// Panics with a message without using an allocator. +/// Useful when using the allocator should be avoided or it is +/// inaccessible. The default [std::panic] performs allocations and so +/// will cause a double panic without a meaningful message if the +/// allocator can't be used. This function makes sure we can panic with +/// a reasonable message even without the allocator working. +fn allocation_free_panic(message: &'static str) -> ! { + use std::os::unix::io::AsRawFd; + + let _ = nix::unistd::write(std::io::stderr().as_raw_fd(), message.as_bytes()); + + std::process::abort(); +} + +const REDIS_ALLOCATOR_NOT_AVAILABLE_MESSAGE: &str = + "Critical error: the Redis Allocator isn't available. +Consider enabling the \"fallback_to_system_allocator\" feature.\n"; + +/// Defines the Redis allocator. This allocator delegates the allocation +/// and deallocation tasks to the Redis server when available, otherwise +/// it fallbacks to the default Rust [std::alloc::System] allocator +/// which is always available compared to the Redis allocator. +#[derive(Copy, Clone)] pub struct RedisAlloc; unsafe impl GlobalAlloc for RedisAlloc { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - /* - * To make sure the memory allocation by Redis is aligned to the according to the layout, - * we need to align the size of the allocation to the layout. - * - * "Memory is conceptually broken into equal-sized chunks, - * where the chunk size is a power of two that is greater than the page size. - * Chunks are always aligned to multiples of the chunk size. - * This alignment makes it possible to find metadata for user objects very quickly." - * - * From: https://linux.die.net/man/3/jemalloc - */ - let size = (layout.size() + layout.align() - 1) & (!(layout.align() - 1)); - - raw::RedisModule_Alloc.unwrap()(size).cast::() + match raw::RedisModule_Alloc { + Some(alloc) => { + /* + * To make sure the memory allocation by Redis is aligned to the + * according to the layout, we need to align the size of the + * allocation to the layout. + * + * "Memory is conceptually broken into equal-sized chunks, + * where the chunk size is a power of two that is greater than + * the page size. Chunks are always aligned to multiples of the + * chunk size. This alignment makes it possible to find metadata + * for user objects very quickly." + * + * From: https://linux.die.net/man/3/jemalloc + */ + let size = (layout.size() + layout.align() - 1) & (!(layout.align() - 1)); + alloc(size).cast() + } + None => allocation_free_panic(REDIS_ALLOCATOR_NOT_AVAILABLE_MESSAGE), + } } unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { - raw::RedisModule_Free.unwrap()(ptr.cast::()) + match raw::RedisModule_Free { + Some(dealloc) => dealloc(ptr.cast()), + None => allocation_free_panic(REDIS_ALLOCATOR_NOT_AVAILABLE_MESSAGE), + } } } diff --git a/src/context/configuration.rs b/src/context/configuration.rs new file mode 100644 index 00000000..da043522 --- /dev/null +++ b/src/context/configuration.rs @@ -0,0 +1,418 @@ +use crate::context::Context; +use crate::raw; +use crate::{RedisError, RedisString}; +use std::ffi::{CStr, CString}; +use std::os::raw::{c_char, c_int, c_longlong, c_void}; + +#[derive(Debug, Copy, Clone)] +pub struct ConfigFlags { + flags: u32, +} + +impl Default for ConfigFlags { + fn default() -> Self { + ConfigFlags { + flags: raw::REDISMODULE_CONFIG_DEFAULT, + } + } +} + +impl ConfigFlags { + pub fn new() -> Self { + Self::default() + } + + pub fn immutable(mut self) -> Self { + self.flags |= raw::REDISMODULE_CONFIG_IMMUTABLE; + self + } + + pub fn is_immutable(&self) -> bool { + self.flags & raw::REDISMODULE_CONFIG_IMMUTABLE != 0 + } + + pub fn sensitive(mut self) -> Self { + self.flags |= raw::REDISMODULE_CONFIG_SENSITIVE; + self + } + + pub fn is_sensitive(&self) -> bool { + self.flags & raw::REDISMODULE_CONFIG_SENSITIVE != 0 + } + + pub fn hidden(mut self) -> Self { + self.flags |= raw::REDISMODULE_CONFIG_HIDDEN; + self + } + + pub fn is_hidden(&self) -> bool { + self.flags & raw::REDISMODULE_CONFIG_HIDDEN != 0 + } + + pub fn protected(mut self) -> Self { + self.flags |= raw::REDISMODULE_CONFIG_PROTECTED; + self + } + + pub fn is_protected(&self) -> bool { + self.flags & raw::REDISMODULE_CONFIG_PROTECTED != 0 + } + + pub fn deny_loading(mut self) -> Self { + self.flags |= raw::REDISMODULE_CONFIG_DENY_LOADING; + self + } + + pub fn is_deny_loading(&self) -> bool { + self.flags & raw::REDISMODULE_CONFIG_DENY_LOADING != 0 + } + + pub fn memory(mut self) -> Self { + self.flags |= raw::REDISMODULE_CONFIG_MEMORY; + self + } + + pub fn is_memoryg(&self) -> bool { + self.flags & raw::REDISMODULE_CONFIG_MEMORY != 0 + } + + pub fn bit_flags(mut self) -> Self { + self.flags |= raw::REDISMODULE_CONFIG_BITFLAGS; + self + } + + pub fn is_bit_flags(&self) -> bool { + self.flags & raw::REDISMODULE_CONFIG_BITFLAGS != 0 + } +} + +pub trait RedisConfigCtx { + fn name(&self) -> &'static str; + fn apply(&self, ctx: &Context) -> Result<(), RedisError>; + fn flags(&self) -> &ConfigFlags; +} + +pub trait RedisStringConfigCtx: RedisConfigCtx { + fn default(&self) -> Option; + fn get(&self, name: &str) -> RedisString; + fn set(&mut self, name: &str, value: RedisString) -> Result<(), RedisError>; +} + +pub trait RedisBoolConfigCtx: RedisConfigCtx { + fn default(&self) -> bool; + fn get(&self, name: &str) -> bool; + fn set(&mut self, name: &str, value: bool) -> Result<(), RedisError>; +} + +pub trait RedisNumberConfigCtx: RedisConfigCtx { + fn default(&self) -> i64; + fn min(&self) -> i64; + fn max(&self) -> i64; + fn get(&self, name: &str) -> i64; + fn set(&mut self, name: &str, value: i64) -> Result<(), RedisError>; +} + +pub trait RedisEnumConfigCtx: RedisConfigCtx { + fn default(&self) -> i32; + fn values(&self) -> Vec<(&str, i32)>; + fn get(&self, name: &str) -> i32; + fn set(&mut self, name: &str, value: i32) -> Result<(), RedisError>; +} + +extern "C" fn internal_string_get( + name: *const c_char, + privdata: *mut c_void, +) -> *mut raw::RedisModuleString { + let redis_config_ctx = unsafe { &*(privdata as *mut C) }; + let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap(); + let res = redis_config_ctx.get(name); + raw::string_retain_string(std::ptr::null_mut(), res.inner); + res.inner +} + +extern "C" fn inner_string_set( + name: *const c_char, + val: *mut raw::RedisModuleString, + privdata: *mut c_void, + err: *mut *mut raw::RedisModuleString, +) -> c_int { + let redis_config_ctx = unsafe { &mut *(privdata as *mut C) }; + let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap(); + let new_val = RedisString::new(std::ptr::null_mut(), val); + match redis_config_ctx.set(name, new_val) { + Ok(_) => raw::REDISMODULE_OK as i32, + Err(e) => { + let err_msg = RedisString::create( + std::ptr::null_mut(), + &format!("Failed setting configuration value `{}`, {}", name, e), + ); + unsafe { + *err = err_msg.inner; + raw::string_retain_string(std::ptr::null_mut(), *err); + } + raw::REDISMODULE_ERR as i32 + } + } +} + +extern "C" fn internal_bool_get( + name: *const c_char, + privdata: *mut c_void, +) -> c_int { + let redis_config_ctx = unsafe { &*(privdata as *mut C) }; + let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap(); + redis_config_ctx.get(name) as c_int +} + +extern "C" fn inner_bool_set( + name: *const c_char, + val: c_int, + privdata: *mut c_void, + err: *mut *mut raw::RedisModuleString, +) -> c_int { + let redis_config_ctx = unsafe { &mut *(privdata as *mut C) }; + let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap(); + match redis_config_ctx.set(name, val > 0) { + Ok(_) => raw::REDISMODULE_OK as i32, + Err(e) => { + let err_msg = RedisString::create( + std::ptr::null_mut(), + &format!("Failed setting configuration value `{}`, {}", name, e), + ); + unsafe { + *err = err_msg.inner; + raw::string_retain_string(std::ptr::null_mut(), *err); + } + raw::REDISMODULE_ERR as i32 + } + } +} + +extern "C" fn internal_number_get( + name: *const c_char, + privdata: *mut c_void, +) -> c_longlong { + let redis_config_ctx = unsafe { &*(privdata as *mut C) }; + let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap(); + redis_config_ctx.get(name) +} + +extern "C" fn inner_number_set( + name: *const c_char, + val: c_longlong, + privdata: *mut c_void, + err: *mut *mut raw::RedisModuleString, +) -> c_int { + let redis_config_ctx = unsafe { &mut *(privdata as *mut C) }; + let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap(); + match redis_config_ctx.set(name, val) { + Ok(_) => raw::REDISMODULE_OK as i32, + Err(e) => { + let err_msg = RedisString::create( + std::ptr::null_mut(), + &format!("Failed setting configuration value `{}`, {}", name, e), + ); + unsafe { + *err = err_msg.inner; + raw::string_retain_string(std::ptr::null_mut(), *err); + } + raw::REDISMODULE_ERR as i32 + } + } +} + +extern "C" fn internal_enum_get( + name: *const c_char, + privdata: *mut c_void, +) -> c_int { + let redis_config_ctx = unsafe { &*(privdata as *mut C) }; + let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap(); + redis_config_ctx.get(name) +} + +extern "C" fn internal_enum_set( + name: *const c_char, + val: c_int, + privdata: *mut c_void, + err: *mut *mut raw::RedisModuleString, +) -> c_int { + let redis_config_ctx = unsafe { &mut *(privdata as *mut C) }; + let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap(); + match redis_config_ctx.set(name, val) { + Ok(_) => raw::REDISMODULE_OK as i32, + Err(e) => { + let err_msg = RedisString::create( + std::ptr::null_mut(), + &format!("Failed setting configuration value `{}`, {}", name, e), + ); + unsafe { + *err = err_msg.inner; + raw::string_retain_string(std::ptr::null_mut(), *err); + } + raw::REDISMODULE_ERR as i32 + } + } +} + +extern "C" fn inner_apply( + ctx: *mut raw::RedisModuleCtx, + privdata: *mut c_void, + err: *mut *mut raw::RedisModuleString, +) -> c_int { + let redis_config_ctx = unsafe { &*(privdata as *mut C) }; + let context = Context::new(ctx); + match redis_config_ctx.apply(&context) { + Ok(_) => raw::REDISMODULE_OK as i32, + Err(e) => { + let err_msg = RedisString::create( + std::ptr::null_mut(), + &format!("Failed apply configuration value, {}", e), + ); + unsafe { + *err = err_msg.inner; + raw::string_retain_string(std::ptr::null_mut(), *err); + } + raw::REDISMODULE_ERR as i32 + } + } +} + +pub fn register_string_configuration( + ctx: &Context, + redis_config_ctx: &C, +) -> Result<(), RedisError> { + let name = match CString::new(redis_config_ctx.name()) { + Ok(n) => n, + Err(e) => return Err(RedisError::String(format!("{}", e))), + }; + let default_val = match redis_config_ctx.default() { + Some(d_v) => match CString::new(d_v) { + Ok(res) => Some(res), + Err(e) => return Err(RedisError::String(format!("{}", e))), + }, + None => None, + }; + let default_val_ptr = match &default_val { + Some(d_v) => d_v.as_ptr(), + None => std::ptr::null_mut(), + }; + let res = unsafe { + raw::RedisModule_RegisterStringConfig.unwrap()( + ctx.ctx, + name.as_ptr(), + default_val_ptr as *const c_char, + redis_config_ctx.flags().flags, + Some(internal_string_get::), + Some(inner_string_set::), + Some(inner_apply::), + redis_config_ctx as *const C as *mut c_void, + ) + }; + + if res != raw::REDISMODULE_OK as i32 { + Err(RedisError::Str("Failed to register config")) + } else { + Ok(()) + } +} + +pub fn register_bool_configuration( + ctx: &Context, + redis_config_ctx: &C, +) -> Result<(), RedisError> { + let name = match CString::new(redis_config_ctx.name()) { + Ok(n) => n, + Err(e) => return Err(RedisError::String(format!("{}", e))), + }; + let res = unsafe { + raw::RedisModule_RegisterBoolConfig.unwrap()( + ctx.ctx, + name.as_ptr(), + redis_config_ctx.default() as c_int, + redis_config_ctx.flags().flags, + Some(internal_bool_get::), + Some(inner_bool_set::), + Some(inner_apply::), + redis_config_ctx as *const C as *mut c_void, + ) + }; + + if res != raw::REDISMODULE_OK as i32 { + Err(RedisError::Str("Failed to register config")) + } else { + Ok(()) + } +} + +pub fn register_numeric_configuration( + ctx: &Context, + redis_config_ctx: &C, +) -> Result<(), RedisError> { + let name = match CString::new(redis_config_ctx.name()) { + Ok(n) => n, + Err(e) => return Err(RedisError::String(format!("{}", e))), + }; + let res = unsafe { + raw::RedisModule_RegisterNumericConfig.unwrap()( + ctx.ctx, + name.as_ptr(), + redis_config_ctx.default(), + redis_config_ctx.flags().flags, + redis_config_ctx.min(), + redis_config_ctx.max(), + Some(internal_number_get::), + Some(inner_number_set::), + Some(inner_apply::), + redis_config_ctx as *const C as *mut c_void, + ) + }; + + if res != raw::REDISMODULE_OK as i32 { + Err(RedisError::Str("Failed to register config")) + } else { + Ok(()) + } +} + +pub fn register_enum_configuration( + ctx: &Context, + redis_config_ctx: &C, +) -> Result<(), RedisError> { + let name = match CString::new(redis_config_ctx.name()) { + Ok(n) => n, + Err(e) => return Err(RedisError::String(format!("{}", e))), + }; + let values = redis_config_ctx.values(); + let mut enum_strings = Vec::new(); + let mut enum_values = Vec::new(); + for (enum_string, val) in values { + enum_strings.push(CString::new(enum_string).unwrap()); + enum_values.push(val); + } + + let res = unsafe { + raw::RedisModule_RegisterEnumConfig.unwrap()( + ctx.ctx, + name.as_ptr(), + redis_config_ctx.default(), + redis_config_ctx.flags().flags, + enum_strings + .iter() + .map(|v| v.as_ptr()) + .collect::>() + .as_ptr() as *mut *const c_char, + enum_values.as_ptr(), + enum_strings.len() as i32, + Some(internal_enum_get::), + Some(internal_enum_set::), + Some(inner_apply::), + redis_config_ctx as *const C as *mut c_void, + ) + }; + + if res != raw::REDISMODULE_OK as i32 { + Err(RedisError::Str("Failed to register config")) + } else { + Ok(()) + } +} diff --git a/src/context/keys_cursor.rs b/src/context/keys_cursor.rs new file mode 100644 index 00000000..efdcca7b --- /dev/null +++ b/src/context/keys_cursor.rs @@ -0,0 +1,69 @@ +use crate::context::Context; +use crate::key::RedisKey; +use crate::raw; +use crate::RedisString; +use std::os::raw::c_void; + +pub struct KeysCursor { + inner_cursor: *mut raw::RedisModuleScanCursor, +} + +impl Default for KeysCursor { + fn default() -> Self { + let inner_cursor = unsafe { raw::RedisModule_ScanCursorCreate.unwrap()() }; + Self { inner_cursor } + } +} + +extern "C" fn scan_callback)>( + ctx: *mut raw::RedisModuleCtx, + keyname: *mut raw::RedisModuleString, + key: *mut raw::RedisModuleKey, + privdata: *mut ::std::os::raw::c_void, +) { + let context = Context::new(ctx); + let key_name = RedisString::new(ctx, keyname); + let mut redis_key = if !key.is_null() { + Some(RedisKey { + ctx, + key_inner: key, + }) + } else { + None + }; + let callback = unsafe { &mut *(privdata as *mut C) }; + callback(&context, key_name, redis_key.as_ref()); + + if redis_key.is_some() { + // we are not the owner of the key so we must not keep it. + redis_key.as_mut().unwrap().key_inner = std::ptr::null_mut(); + } +} + +impl KeysCursor { + pub fn new() -> KeysCursor { + Self::default() + } + + pub fn scan)>( + &self, + ctx: &Context, + callback: &C, + ) -> bool { + let res = unsafe { + raw::RedisModule_Scan.unwrap()( + ctx.ctx, + self.inner_cursor, + Some(scan_callback::), + callback as *const C as *mut c_void, + ) + }; + res != 0 + } +} + +impl Drop for KeysCursor { + fn drop(&mut self) { + unsafe { raw::RedisModule_ScanCursorDestroy.unwrap()(self.inner_cursor) }; + } +} diff --git a/src/context/mod.rs b/src/context/mod.rs index 1e64e2ef..d586ccbd 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -8,6 +8,8 @@ use crate::{add_info_field_long_long, add_info_field_str, raw, utils, Status}; use crate::{add_info_section, LogLevel}; use crate::{RedisError, RedisResult, RedisString, RedisValue}; +use std::collections::{HashMap, HashSet}; + #[cfg(feature = "experimental-api")] use std::ffi::CStr; @@ -22,6 +24,123 @@ pub mod blocked; pub mod info; +pub mod keys_cursor; + +pub mod server_events; + +pub mod configuration; + +#[derive(Clone)] +pub struct CallOptions { + options: String, +} + +// TODO rewrite as a bitfield which is serializable to a string. +// This will help a lot to simplify the code and make it more developer- +// friendly, also will avoid possible duplicates and will consume less +// space, also will be allocated on stack instead of the heap. +#[derive(Debug, Clone)] +pub struct CallOptionsBuilder { + options: String, +} + +impl Default for CallOptionsBuilder { + fn default() -> Self { + CallOptionsBuilder { + options: "v".to_string(), + } + } +} + +impl CallOptionsBuilder { + pub fn new() -> CallOptionsBuilder { + Self::default() + } + + fn add_flag(&mut self, flag: &str) { + self.options.push_str(flag); + } + + pub fn no_writes(mut self) -> CallOptionsBuilder { + self.add_flag("W"); + self + } + + pub fn script_mode(mut self) -> CallOptionsBuilder { + self.add_flag("S"); + self + } + + pub fn verify_acl(mut self) -> CallOptionsBuilder { + self.add_flag("C"); + self + } + + pub fn verify_oom(mut self) -> CallOptionsBuilder { + self.add_flag("M"); + self + } + + pub fn errors_as_replies(mut self) -> CallOptionsBuilder { + self.add_flag("E"); + self + } + + pub fn replicate(mut self) -> CallOptionsBuilder { + self.add_flag("!"); + self + } + + pub fn resp_3(mut self) -> CallOptionsBuilder { + self.add_flag("3"); + self + } + + pub fn constract(&self) -> CallOptions { + let mut res = CallOptions { + options: self.options.to_string(), + }; + // TODO don't "make" it a C string, just use a [CString]. + res.options.push('\0'); /* make it C string */ + res + } +} + +// TODO rewrite using the bit_fields crate. +#[derive(Debug, Default, Copy, Clone)] +pub struct AclPermissions { + flags: u32, +} + +impl AclPermissions { + pub fn new() -> AclPermissions { + Self::default() + } + + pub fn add_access_permission(&mut self) { + self.flags |= raw::REDISMODULE_CMD_KEY_ACCESS; + } + + pub fn add_insert_permission(&mut self) { + self.flags |= raw::REDISMODULE_CMD_KEY_INSERT; + } + + pub fn add_delete_permission(&mut self) { + self.flags |= raw::REDISMODULE_CMD_KEY_DELETE; + } + + pub fn add_update_permission(&mut self) { + self.flags |= raw::REDISMODULE_CMD_KEY_UPDATE; + } + + pub fn add_full_permission(&mut self) { + self.add_access_permission(); + self.add_insert_permission(); + self.add_delete_permission(); + self.add_update_permission(); + } +} + /// `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 { @@ -75,9 +194,8 @@ impl Context { #[must_use] pub fn is_keys_position_request(&self) -> bool { // We want this to be available in tests where we don't have an actual Redis to call - if cfg!(feature = "test") { - return false; - } + #[cfg(test)] + return false; let result = unsafe { raw::RedisModule_IsKeysPositionRequest.unwrap()(self.ctx) }; @@ -95,10 +213,22 @@ impl Context { } } - pub fn call(&self, command: &str, args: &[&str]) -> RedisResult { + // The lint is disabled since all the behaviour is controlled via Redis, + // and all the pointers if dereferenced will be dereferenced by the module. + // + // Since we can't know the logic of Redis when it comes to pointers, we + // can't say whether passing a null pointer is okay to a redis function + // or not. So we can neither deny it is valid nor confirm. + #[allow(clippy::not_unsafe_ptr_arg_deref)] + pub fn call_internal( + &self, + command: &str, + options: *const c_char, + args: &[&[u8]], + ) -> RedisResult { let terminated_args: Vec = args .iter() - .map(|s| RedisString::create(self.ctx, s)) + .map(|s| RedisString::create_from_slice(self.ctx, s)) .collect(); let inner_args: Vec<*mut raw::RedisModuleString> = @@ -110,7 +240,7 @@ impl Context { p_call( self.ctx, cmd.as_ptr(), - raw::FMT, + options, inner_args.as_ptr() as *mut c_char, terminated_args.len(), ) @@ -122,6 +252,18 @@ impl Context { result } + pub fn call_ext(&self, command: &str, options: &CallOptions, args: &[&[u8]]) -> RedisResult { + self.call_internal(command, options.options.as_ptr() as *const c_char, args) + } + + pub fn call(&self, command: &str, args: &[&str]) -> RedisResult { + self.call_internal( + command, + raw::FMT, + &args.iter().map(|v| v.as_bytes()).collect::>(), + ) + } + fn parse_call_reply(reply: *mut raw::RedisModuleCallReply) -> RedisResult { match raw::call_reply_type(reply) { raw::ReplyType::Error => Err(RedisError::String(raw::call_reply_string(reply))), @@ -137,8 +279,67 @@ impl Context { Ok(RedisValue::Array(vec)) } raw::ReplyType::Integer => Ok(RedisValue::Integer(raw::call_reply_integer(reply))), - raw::ReplyType::String => Ok(RedisValue::SimpleString(raw::call_reply_string(reply))), + raw::ReplyType::String => Ok(RedisValue::StringBuffer({ + let mut len: usize = 0; + let buff = raw::call_reply_string_ptr(reply, &mut len); + unsafe { std::slice::from_raw_parts(buff as *mut u8, len) }.to_vec() + })), raw::ReplyType::Null => Ok(RedisValue::Null), + raw::ReplyType::Map => { + let length = raw::call_reply_length(reply); + let mut map = HashMap::new(); + for i in 0..length { + let (key, val) = raw::call_reply_map_element(reply, i); + let key = Self::parse_call_reply(key)?; + let val = Self::parse_call_reply(val)?; + // The numbers are converted to a string, it is probably + // good enough for most usecases and the effort to support + // it as number is big. + let key = match key { + RedisValue::SimpleString(s) => s.as_bytes().to_vec(), + RedisValue::SimpleStringStatic(s) => s.as_bytes().to_vec(), + RedisValue::BulkString(s) => s.as_bytes().to_vec(), + RedisValue::BulkRedisString(s) => s.as_slice().to_vec(), + RedisValue::Integer(i) => i.to_string().as_bytes().to_vec(), + RedisValue::Float(f) => f.to_string().as_bytes().to_vec(), + RedisValue::StringBuffer(b) => b, + _ => return Err(RedisError::Str("type is not supported as map key")), + }; + map.insert(key, val); + } + Ok(RedisValue::Map(map)) + } + raw::ReplyType::Set => { + let length = raw::call_reply_length(reply); + let mut set = HashSet::new(); + for i in 0..length { + let val = raw::call_reply_set_element(reply, i); + let val = Self::parse_call_reply(val)?; + // The numbers are converted to a string, it is probably + // good enough for most usecases and the effort to support + // it as number is big. + let val = match val { + RedisValue::SimpleString(s) => s.as_bytes().to_vec(), + RedisValue::SimpleStringStatic(s) => s.as_bytes().to_vec(), + RedisValue::BulkString(s) => s.as_bytes().to_vec(), + RedisValue::BulkRedisString(s) => s.as_slice().to_vec(), + RedisValue::Integer(i) => i.to_string().as_bytes().to_vec(), + RedisValue::Float(f) => f.to_string().as_bytes().to_vec(), + RedisValue::StringBuffer(b) => b, + _ => return Err(RedisError::Str("type is not supported on set")), + }; + set.insert(val); + } + Ok(RedisValue::Set(set)) + } + raw::ReplyType::Bool => Ok(RedisValue::Bool(raw::call_reply_bool(reply) != 0)), + raw::ReplyType::Double => Ok(RedisValue::Double(raw::call_reply_double(reply))), + raw::ReplyType::BigNumber => { + Ok(RedisValue::BigNumber(raw::call_reply_big_numebr(reply))) + } + raw::ReplyType::VerbatimString => Ok(RedisValue::VerbatimString( + raw::call_reply_verbatim_string(reply), + )), } } @@ -155,12 +356,56 @@ impl Context { .unwrap() } + #[allow(clippy::must_use_candidate)] + pub fn reply_null(&self) -> raw::Status { + unsafe { raw::RedisModule_ReplyWithNull.unwrap()(self.ctx).into() } + } + #[allow(clippy::must_use_candidate)] pub fn reply_simple_string(&self, s: &str) -> raw::Status { let msg = Self::str_as_legal_resp_string(s); unsafe { raw::RedisModule_ReplyWithSimpleString.unwrap()(self.ctx, msg.as_ptr()).into() } } + #[allow(clippy::must_use_candidate)] + pub fn reply_bulk_string(&self, s: &str) -> raw::Status { + unsafe { + raw::RedisModule_ReplyWithStringBuffer.unwrap()( + self.ctx, + s.as_ptr() as *mut c_char, + s.len(), + ) + .into() + } + } + + #[allow(clippy::must_use_candidate)] + pub fn reply_bulk_slice(&self, s: &[u8]) -> raw::Status { + unsafe { + raw::RedisModule_ReplyWithStringBuffer.unwrap()( + self.ctx, + s.as_ptr() as *mut c_char, + s.len(), + ) + .into() + } + } + + #[allow(clippy::must_use_candidate)] + pub fn reply_array(&self, size: usize) -> raw::Status { + unsafe { raw::RedisModule_ReplyWithArray.unwrap()(self.ctx, size as c_long).into() } + } + + #[allow(clippy::must_use_candidate)] + pub fn reply_long(&self, l: i64) -> raw::Status { + unsafe { raw::RedisModule_ReplyWithLongLong.unwrap()(self.ctx, l as c_longlong).into() } + } + + #[allow(clippy::must_use_candidate)] + pub fn reply_double(&self, d: f64) -> raw::Status { + unsafe { raw::RedisModule_ReplyWithDouble.unwrap()(self.ctx, d).into() } + } + #[allow(clippy::must_use_candidate)] pub fn reply_error_string(&self, s: &str) -> raw::Status { let msg = Self::str_as_legal_resp_string(s); @@ -194,8 +439,8 @@ impl Context { Ok(RedisValue::BulkString(s)) => unsafe { raw::RedisModule_ReplyWithStringBuffer.unwrap()( self.ctx, - s.as_ptr().cast::(), - s.len() as usize, + s.as_ptr().cast(), + s.len(), ) .into() }, @@ -207,8 +452,8 @@ impl Context { Ok(RedisValue::StringBuffer(s)) => unsafe { raw::RedisModule_ReplyWithStringBuffer.unwrap()( self.ctx, - s.as_ptr().cast::(), - s.len() as usize, + s.as_ptr().cast(), + s.len(), ) .into() }, @@ -227,6 +472,70 @@ impl Context { raw::Status::Ok } + Ok(RedisValue::Map(map)) => { + unsafe { + raw::RedisModule_ReplyWithMap.unwrap()(self.ctx, map.len() as c_long); + } + + for (key, val) in map { + unsafe { + raw::RedisModule_ReplyWithStringBuffer.unwrap()( + self.ctx, + key.as_ptr().cast(), + key.len(), + ); + }; + self.reply(Ok(val)); + } + + raw::Status::Ok + } + + Ok(RedisValue::Set(set)) => { + unsafe { + raw::RedisModule_ReplyWithSet.unwrap()(self.ctx, set.len() as c_long); + } + + for val in set { + unsafe { + raw::RedisModule_ReplyWithStringBuffer.unwrap()( + self.ctx, + val.as_ptr().cast(), + val.len(), + ); + }; + } + + raw::Status::Ok + } + + Ok(RedisValue::Bool(b)) => unsafe { + raw::RedisModule_ReplyWithBool.unwrap()(self.ctx, b as c_int).into() + }, + + Ok(RedisValue::Double(d)) => unsafe { + raw::RedisModule_ReplyWithDouble.unwrap()(self.ctx, d).into() + }, + + Ok(RedisValue::BigNumber(s)) => unsafe { + raw::RedisModule_ReplyWithBigNumber.unwrap()( + self.ctx, + s.as_ptr() as *mut c_char, + s.len(), + ) + .into() + }, + + Ok(RedisValue::VerbatimString((t, s))) => unsafe { + raw::RedisModule_ReplyWithVerbatimStringType.unwrap()( + self.ctx, + s.as_ptr() as *mut c_char, + s.len(), + t.as_ptr() as *mut c_char, + ) + .into() + }, + Ok(RedisValue::Null) => unsafe { raw::RedisModule_ReplyWithNull.unwrap()(self.ctx).into() }, @@ -271,6 +580,11 @@ impl Context { RedisString::create(self.ctx, s) } + #[must_use] + pub fn create_string_from_slice(&self, s: &[u8]) -> RedisString { + RedisString::create_from_slice(self.ctx, s) + } + #[must_use] pub const fn get_raw(&self) -> *mut raw::RedisModuleCtx { self.ctx @@ -316,23 +630,25 @@ impl Context { } /// Returns the redis version by calling "info server" API and parsing the reply - #[cfg(feature = "test")] pub fn get_redis_version_rm_call(&self) -> Result { self.get_redis_version_internal(true) } pub fn version_from_info(info: RedisValue) -> Result { - if let RedisValue::SimpleString(info_str) = info { - if let Some(ver) = utils::get_regexp_captures( - info_str.as_str(), - r"(?m)\bredis_version:([0-9]+)\.([0-9]+)\.([0-9]+)\b", - ) { - return Ok(Version { - major: ver[0].parse::().unwrap(), - minor: ver[1].parse::().unwrap(), - patch: ver[2].parse::().unwrap(), - }); - } + let info_str = match info { + RedisValue::SimpleString(info_str) => info_str, + RedisValue::StringBuffer(b) => std::str::from_utf8(&b).unwrap().to_string(), + _ => return Err(RedisError::Str("Error getting redis_version")), + }; + if let Some(ver) = utils::get_regexp_captures( + info_str.as_str(), + r"(?m)\bredis_version:([0-9]+)\.([0-9]+)\.([0-9]+)\b", + ) { + return Ok(Version { + major: ver[0].parse::().unwrap(), + minor: ver[1].parse::().unwrap(), + patch: ver[2].parse::().unwrap(), + }); } Err(RedisError::Str("Error getting redis_version")) } @@ -354,9 +670,76 @@ impl Context { } } } + pub fn set_module_options(&self, options: ModuleOptions) { unsafe { raw::RedisModule_SetModuleOptions.unwrap()(self.ctx, options.bits()) }; } + + pub fn is_primary(&self) -> bool { + let flags = unsafe { raw::RedisModule_GetContextFlags.unwrap()(self.ctx) }; + flags as u32 & raw::REDISMODULE_CTX_FLAGS_MASTER != 0 + } + + pub fn is_oom(&self) -> bool { + let flags = unsafe { raw::RedisModule_GetContextFlags.unwrap()(self.ctx) }; + flags as u32 & raw::REDISMODULE_CTX_FLAGS_OOM != 0 + } + + pub fn allow_block(&self) -> bool { + let flags = unsafe { raw::RedisModule_GetContextFlags.unwrap()(self.ctx) }; + (flags as u32 & raw::REDISMODULE_CTX_FLAGS_DENY_BLOCKING) == 0 + } + + pub fn get_current_user(&self) -> Result { + let user = unsafe { raw::RedisModule_GetCurrentUserName.unwrap()(self.ctx) }; + let user = RedisString::from_redis_module_string(ptr::null_mut(), user); + Ok(user.try_as_str()?.to_string()) + } + + pub fn autenticate_user(&self, user_name: &str) -> raw::Status { + if unsafe { + raw::RedisModule_AuthenticateClientWithACLUser.unwrap()( + self.ctx, + user_name.as_ptr() as *const c_char, + user_name.len(), + None, + ptr::null_mut(), + ptr::null_mut(), + ) + } == raw::REDISMODULE_OK as i32 + { + raw::Status::Ok + } else { + raw::Status::Err + } + } + + pub fn acl_check_key_permission( + &self, + user_name: &str, + key_name: &RedisString, + permissions: &AclPermissions, + ) -> Result<(), RedisError> { + let user_name = RedisString::create(self.ctx, user_name); + let user = unsafe { raw::RedisModule_GetModuleUserFromUserName.unwrap()(user_name.inner) }; + if user.is_null() { + return Err(RedisError::Str("User does not exists or disabled")); + } + if unsafe { + raw::RedisModule_ACLCheckKeyPermissions.unwrap()( + user, + key_name.inner, + permissions.flags as i32, + ) + } == raw::REDISMODULE_OK as i32 + { + unsafe { raw::RedisModule_FreeModuleUser.unwrap()(user) }; + Ok(()) + } else { + unsafe { raw::RedisModule_FreeModuleUser.unwrap()(user) }; + Err(RedisError::Str("User does not have permissions on key")) + } + } } pub struct InfoContext { diff --git a/src/context/server_events.rs b/src/context/server_events.rs new file mode 100644 index 00000000..1825f7d6 --- /dev/null +++ b/src/context/server_events.rs @@ -0,0 +1,263 @@ +use crate::context::Context; +use crate::raw; +use crate::RedisError; + +#[derive(Clone)] +pub enum ServerRole { + Primary, + Replica, +} + +#[derive(Clone)] +pub enum LoadingSubevent { + RdbStarted, + AofStarted, + ReplStarted, + Ended, + Failed, +} + +#[derive(Clone)] +pub enum FlushSubevent { + Started, + Ended, +} + +#[derive(Clone)] +pub enum ModuleChangeSubevent { + Loaded, + Unloaded, +} + +#[derive(Clone)] +pub struct RoleChangedEventData { + pub role: ServerRole, +} + +#[derive(Clone)] +pub enum ServerEventData { + RoleChangedEvent(RoleChangedEventData), + LoadingEvent(LoadingSubevent), + FlushEvent(FlushSubevent), + ModuleChange(ModuleChangeSubevent), +} + +pub enum ServerEvents { + RuleChanged, + Loading, + Flush, + ModuleChange, +} + +pub type ServerEventCallback = Box; + +pub struct Subscribers { + list: Option>, + event_callback: raw::RedisModuleEventCallback, + event: raw::RedisModuleEvent, + event_str_rep: &'static str, +} + +impl Subscribers { + fn get_subscribers_list(&self) -> Option<&Vec> { + self.list.as_ref() + } + + fn get_or_create_subscribers_list( + &mut self, + ctx: &Context, + ) -> Result<&mut Vec, RedisError> { + if self.get_subscribers_list().is_none() { + unsafe { + if raw::RedisModule_SubscribeToServerEvent.unwrap()( + ctx.ctx, + self.event, + self.event_callback, + ) != raw::REDISMODULE_OK as i32 + { + return Err(RedisError::String(format!( + "Failed subscribing to server event: '{}'", + self.event_str_rep + ))); + } + self.list = Some(Vec::new()); + } + } + Ok(self.list.as_mut().unwrap()) + } + + fn subscribe_to_event( + &mut self, + ctx: &Context, + callback: ServerEventCallback, + ) -> Result<(), RedisError> { + let subscribers_list = self.get_or_create_subscribers_list(ctx)?; + subscribers_list.push(callback); + Ok(()) + } + + fn get_subscribers(&self) -> &Vec { + self.get_subscribers_list().unwrap() + } +} + +static mut ROLE_CHANGED_SUBSCRIBERS: Subscribers = Subscribers { + list: None, + event_callback: Some(role_changed_callback), + event: raw::RedisModuleEvent { + id: raw::REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED, + dataver: 1, + }, + event_str_rep: "role_changed", +}; + +extern "C" fn role_changed_callback( + ctx: *mut raw::RedisModuleCtx, + _eid: raw::RedisModuleEvent, + subevent: u64, + _data: *mut ::std::os::raw::c_void, +) { + let new_role = if subevent == raw::REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER { + ServerRole::Primary + } else { + ServerRole::Replica + }; + let ctx = Context::new(ctx); + for callback in unsafe { &ROLE_CHANGED_SUBSCRIBERS }.get_subscribers() { + callback( + &ctx, + ServerEventData::RoleChangedEvent(RoleChangedEventData { + role: new_role.clone(), + }), + ); + } +} + +fn subscribe_to_role_changed_event( + ctx: &Context, + callback: ServerEventCallback, +) -> Result<(), RedisError> { + unsafe { &mut ROLE_CHANGED_SUBSCRIBERS }.subscribe_to_event(ctx, callback) +} + +static mut LOADING_SUBSCRIBERS: Subscribers = Subscribers { + list: None, + event_callback: Some(loading_event_callback), + event: raw::RedisModuleEvent { + id: raw::REDISMODULE_EVENT_LOADING, + dataver: 1, + }, + event_str_rep: "loading", +}; + +extern "C" fn loading_event_callback( + ctx: *mut raw::RedisModuleCtx, + _eid: raw::RedisModuleEvent, + subevent: u64, + _data: *mut ::std::os::raw::c_void, +) { + let loading_sub_event = match subevent { + raw::REDISMODULE_SUBEVENT_LOADING_RDB_START => LoadingSubevent::RdbStarted, + raw::REDISMODULE_SUBEVENT_LOADING_REPL_START => LoadingSubevent::ReplStarted, + raw::REDISMODULE_SUBEVENT_LOADING_ENDED => LoadingSubevent::Ended, + _ => LoadingSubevent::Failed, + }; + let ctx = Context::new(ctx); + for callback in unsafe { &LOADING_SUBSCRIBERS }.get_subscribers() { + callback( + &ctx, + ServerEventData::LoadingEvent(loading_sub_event.clone()), + ); + } +} + +fn subscribe_to_loading_event( + ctx: &Context, + callback: ServerEventCallback, +) -> Result<(), RedisError> { + unsafe { &mut LOADING_SUBSCRIBERS }.subscribe_to_event(ctx, callback) +} + +static mut FLUSH_SUBSCRIBERS: Subscribers = Subscribers { + list: None, + event_callback: Some(flush_event_callback), + event: raw::RedisModuleEvent { + id: raw::REDISMODULE_EVENT_FLUSHDB, + dataver: 1, + }, + event_str_rep: "flush", +}; + +extern "C" fn flush_event_callback( + ctx: *mut raw::RedisModuleCtx, + _eid: raw::RedisModuleEvent, + subevent: u64, + _data: *mut ::std::os::raw::c_void, +) { + let flush_sub_event = if subevent == raw::REDISMODULE_SUBEVENT_FLUSHDB_START { + FlushSubevent::Started + } else { + FlushSubevent::Ended + }; + let ctx = Context::new(ctx); + for callback in unsafe { &FLUSH_SUBSCRIBERS }.get_subscribers() { + callback(&ctx, ServerEventData::FlushEvent(flush_sub_event.clone())); + } +} + +fn subscribe_to_flush_event( + ctx: &Context, + callback: ServerEventCallback, +) -> Result<(), RedisError> { + unsafe { &mut FLUSH_SUBSCRIBERS }.subscribe_to_event(ctx, callback) +} + +static mut MODULE_LOAD_SUBSCRIBERS: Subscribers = Subscribers { + list: None, + event_callback: Some(module_change_event_callback), + event: raw::RedisModuleEvent { + id: raw::REDISMODULE_EVENT_MODULE_CHANGE, + dataver: 1, + }, + event_str_rep: "module_change", +}; + +extern "C" fn module_change_event_callback( + ctx: *mut raw::RedisModuleCtx, + _eid: raw::RedisModuleEvent, + subevent: u64, + _data: *mut ::std::os::raw::c_void, +) { + let module_changed_sub_event = if subevent == raw::REDISMODULE_SUBEVENT_MODULE_LOADED { + ModuleChangeSubevent::Loaded + } else { + ModuleChangeSubevent::Unloaded + }; + let ctx = Context::new(ctx); + for callback in unsafe { &MODULE_LOAD_SUBSCRIBERS }.get_subscribers() { + callback( + &ctx, + ServerEventData::ModuleChange(module_changed_sub_event.clone()), + ); + } +} + +fn subscribe_to_module_change_event( + ctx: &Context, + callback: ServerEventCallback, +) -> Result<(), RedisError> { + unsafe { &mut MODULE_LOAD_SUBSCRIBERS }.subscribe_to_event(ctx, callback) +} + +pub fn subscribe_to_server_event( + ctx: &Context, + event: ServerEvents, + callback: ServerEventCallback, +) -> Result<(), RedisError> { + match event { + ServerEvents::RuleChanged => subscribe_to_role_changed_event(ctx, callback), + ServerEvents::Loading => subscribe_to_loading_event(ctx, callback), + ServerEvents::Flush => subscribe_to_flush_event(ctx, callback), + ServerEvents::ModuleChange => subscribe_to_module_change_event(ctx, callback), + } +} diff --git a/src/context/thread_safe.rs b/src/context/thread_safe.rs index a4e5f7a4..45e42122 100644 --- a/src/context/thread_safe.rs +++ b/src/context/thread_safe.rs @@ -70,6 +70,10 @@ impl ThreadSafeContext { let ctx = Context::new(self.ctx); ctx.reply(r) } + + pub fn get_ctx(&self) -> Context { + Context::new(self.ctx) + } } impl ThreadSafeContext { diff --git a/src/include/redismodule.h b/src/include/redismodule.h index 678444ed..bc1bbc72 100644 --- a/src/include/redismodule.h +++ b/src/include/redismodule.h @@ -4,6 +4,7 @@ #include #include #include +#include /* ---------------- Defines common between core and modules --------------- */ @@ -16,7 +17,7 @@ /* Version of the RedisModuleTypeMethods structure. Once the RedisModuleTypeMethods * structure is changed, this version number needs to be changed synchronistically. */ -#define REDISMODULE_TYPE_METHOD_VERSION 3 +#define REDISMODULE_TYPE_METHOD_VERSION 4 /* API flags and constants */ #define REDISMODULE_READ (1<<0) @@ -26,6 +27,7 @@ * Avoid touching the LRU/LFU of the key when opened. */ #define REDISMODULE_OPEN_KEY_NOTOUCH (1<<16) +/* List push and pop */ #define REDISMODULE_LIST_HEAD 0 #define REDISMODULE_LIST_TAIL 1 @@ -46,9 +48,17 @@ #define REDISMODULE_REPLY_INTEGER 2 #define REDISMODULE_REPLY_ARRAY 3 #define REDISMODULE_REPLY_NULL 4 +#define REDISMODULE_REPLY_MAP 5 +#define REDISMODULE_REPLY_SET 6 +#define REDISMODULE_REPLY_BOOL 7 +#define REDISMODULE_REPLY_DOUBLE 8 +#define REDISMODULE_REPLY_BIG_NUMBER 9 +#define REDISMODULE_REPLY_VERBATIM_STRING 10 +#define REDISMODULE_REPLY_ATTRIBUTE 11 /* Postponed array length. */ -#define REDISMODULE_POSTPONED_ARRAY_LEN -1 +#define REDISMODULE_POSTPONED_ARRAY_LEN -1 /* Deprecated, please use REDISMODULE_POSTPONED_LEN */ +#define REDISMODULE_POSTPONED_LEN -1 /* Expire */ #define REDISMODULE_NO_EXPIRE -1 @@ -70,6 +80,16 @@ #define REDISMODULE_HASH_EXISTS (1<<3) #define REDISMODULE_HASH_COUNT_ALL (1<<4) +#define REDISMODULE_CONFIG_DEFAULT 0 /* This is the default for a module config. */ +#define REDISMODULE_CONFIG_IMMUTABLE (1ULL<<0) /* Can this value only be set at startup? */ +#define REDISMODULE_CONFIG_SENSITIVE (1ULL<<1) /* Does this value contain sensitive information */ +#define REDISMODULE_CONFIG_HIDDEN (1ULL<<4) /* This config is hidden in `config get ` (used for tests/debugging) */ +#define REDISMODULE_CONFIG_PROTECTED (1ULL<<5) /* Becomes immutable if enable-protected-configs is enabled. */ +#define REDISMODULE_CONFIG_DENY_LOADING (1ULL<<6) /* This config is forbidden during loading. */ + +#define REDISMODULE_CONFIG_MEMORY (1ULL<<7) /* Indicates if this value can be set as a memory value */ +#define REDISMODULE_CONFIG_BITFLAGS (1ULL<<8) /* Indicates if this value can be set as a multiple enum values */ + /* StreamID type. */ typedef struct RedisModuleStreamID { uint64_t ms; @@ -138,11 +158,15 @@ typedef struct RedisModuleStreamID { /* The current client does not allow blocking, either called from * within multi, lua, or from another module using RM_Call */ #define REDISMODULE_CTX_FLAGS_DENY_BLOCKING (1<<21) +/* The current client uses RESP3 protocol */ +#define REDISMODULE_CTX_FLAGS_RESP3 (1<<22) +/* Redis is currently async loading database for diskless replication. */ +#define REDISMODULE_CTX_FLAGS_ASYNC_LOADING (1<<23) /* Next context flag, must be updated when adding new flags above! This flag should not be used directly by the module. * Use RedisModule_GetContextFlagsAll instead. */ -#define _REDISMODULE_CTX_FLAGS_NEXT (1<<22) +#define _REDISMODULE_CTX_FLAGS_NEXT (1<<24) /* Keyspace changes notification classes. Every class is associated with a * character for configuration purposes. @@ -161,11 +185,12 @@ This flag should not be used directly by the module. #define REDISMODULE_NOTIFY_KEY_MISS (1<<11) /* m (Note: This one is excluded from REDISMODULE_NOTIFY_ALL on purpose) */ #define REDISMODULE_NOTIFY_LOADED (1<<12) /* module only key space notification, indicate a key loaded from rdb */ #define REDISMODULE_NOTIFY_MODULE (1<<13) /* d, module key space notification */ +#define REDISMODULE_NOTIFY_NEW (1<<14) /* n, new key notification */ /* Next notification flag, must be updated when adding new flags above! This flag should not be used directly by the module. * Use RedisModule_GetKeyspaceNotificationFlagsAll instead. */ -#define _REDISMODULE_NOTIFY_NEXT (1<<14) +#define _REDISMODULE_NOTIFY_NEXT (1<<15) #define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM | REDISMODULE_NOTIFY_MODULE) /* A */ @@ -204,6 +229,10 @@ This flag should not be used directly by the module. #define REDISMODULE_AUX_BEFORE_RDB (1<<0) #define REDISMODULE_AUX_AFTER_RDB (1<<1) +/* RM_Yield flags */ +#define REDISMODULE_YIELD_FLAG_NONE (1<<0) +#define REDISMODULE_YIELD_FLAG_CLIENTS (1<<1) + /* This type represents a timer handle, and is returned when a timer is * registered and used in order to invalidate a timer. It's just a 64 bit * number, because this is how each timer is represented inside the radix tree @@ -217,14 +246,185 @@ typedef uint64_t RedisModuleTimerID; /* Declare that the module can handle errors with RedisModule_SetModuleOptions. */ #define REDISMODULE_OPTIONS_HANDLE_IO_ERRORS (1<<0) + /* When set, Redis will not call RedisModule_SignalModifiedKey(), implicitly in * RedisModule_CloseKey, and the module needs to do that when manually when keys - * are modified from the user's sperspective, to invalidate WATCH. */ + * are modified from the user's perspective, to invalidate WATCH. */ #define REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED (1<<1) +/* Declare that the module can handle diskless async replication with RedisModule_SetModuleOptions. */ +#define REDISMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD (1<<2) + +/* Definitions for RedisModule_SetCommandInfo. */ + +typedef enum { + REDISMODULE_ARG_TYPE_STRING, + REDISMODULE_ARG_TYPE_INTEGER, + REDISMODULE_ARG_TYPE_DOUBLE, + REDISMODULE_ARG_TYPE_KEY, /* A string, but represents a keyname */ + REDISMODULE_ARG_TYPE_PATTERN, + REDISMODULE_ARG_TYPE_UNIX_TIME, + REDISMODULE_ARG_TYPE_PURE_TOKEN, + REDISMODULE_ARG_TYPE_ONEOF, /* Must have sub-arguments */ + REDISMODULE_ARG_TYPE_BLOCK /* Must have sub-arguments */ +} RedisModuleCommandArgType; + +#define REDISMODULE_CMD_ARG_NONE (0) +#define REDISMODULE_CMD_ARG_OPTIONAL (1<<0) /* The argument is optional (like GET in SET command) */ +#define REDISMODULE_CMD_ARG_MULTIPLE (1<<1) /* The argument may repeat itself (like key in DEL) */ +#define REDISMODULE_CMD_ARG_MULTIPLE_TOKEN (1<<2) /* The argument may repeat itself, and so does its token (like `GET pattern` in SORT) */ +#define _REDISMODULE_CMD_ARG_NEXT (1<<3) + +typedef enum { + REDISMODULE_KSPEC_BS_INVALID = 0, /* Must be zero. An implicitly value of + * zero is provided when the field is + * absent in a struct literal. */ + REDISMODULE_KSPEC_BS_UNKNOWN, + REDISMODULE_KSPEC_BS_INDEX, + REDISMODULE_KSPEC_BS_KEYWORD +} RedisModuleKeySpecBeginSearchType; + +typedef enum { + REDISMODULE_KSPEC_FK_OMITTED = 0, /* Used when the field is absent in a + * struct literal. Don't use this value + * explicitly. */ + REDISMODULE_KSPEC_FK_UNKNOWN, + REDISMODULE_KSPEC_FK_RANGE, + REDISMODULE_KSPEC_FK_KEYNUM +} RedisModuleKeySpecFindKeysType; + +/* Key-spec flags. For details, see the documentation of + * RedisModule_SetCommandInfo and the key-spec flags in server.h. */ +#define REDISMODULE_CMD_KEY_RO (1ULL<<0) +#define REDISMODULE_CMD_KEY_RW (1ULL<<1) +#define REDISMODULE_CMD_KEY_OW (1ULL<<2) +#define REDISMODULE_CMD_KEY_RM (1ULL<<3) +#define REDISMODULE_CMD_KEY_ACCESS (1ULL<<4) +#define REDISMODULE_CMD_KEY_UPDATE (1ULL<<5) +#define REDISMODULE_CMD_KEY_INSERT (1ULL<<6) +#define REDISMODULE_CMD_KEY_DELETE (1ULL<<7) +#define REDISMODULE_CMD_KEY_NOT_KEY (1ULL<<8) +#define REDISMODULE_CMD_KEY_INCOMPLETE (1ULL<<9) +#define REDISMODULE_CMD_KEY_VARIABLE_FLAGS (1ULL<<10) + +/* Channel flags, for details see the documentation of + * RedisModule_ChannelAtPosWithFlags. */ +#define REDISMODULE_CMD_CHANNEL_PATTERN (1ULL<<0) +#define REDISMODULE_CMD_CHANNEL_PUBLISH (1ULL<<1) +#define REDISMODULE_CMD_CHANNEL_SUBSCRIBE (1ULL<<2) +#define REDISMODULE_CMD_CHANNEL_UNSUBSCRIBE (1ULL<<3) + +typedef struct RedisModuleCommandArg { + const char *name; + RedisModuleCommandArgType type; + int key_spec_index; /* If type is KEY, this is a zero-based index of + * the key_spec in the command. For other types, + * you may specify -1. */ + const char *token; /* If type is PURE_TOKEN, this is the token. */ + const char *summary; + const char *since; + int flags; /* The REDISMODULE_CMD_ARG_* macros. */ + const char *deprecated_since; + struct RedisModuleCommandArg *subargs; +} RedisModuleCommandArg; + +typedef struct { + const char *since; + const char *changes; +} RedisModuleCommandHistoryEntry; + +typedef struct { + const char *notes; + uint64_t flags; /* REDISMODULE_CMD_KEY_* macros. */ + RedisModuleKeySpecBeginSearchType begin_search_type; + union { + struct { + /* The index from which we start the search for keys */ + int pos; + } index; + struct { + /* The keyword that indicates the beginning of key args */ + const char *keyword; + /* An index in argv from which to start searching. + * Can be negative, which means start search from the end, in reverse + * (Example: -2 means to start in reverse from the penultimate arg) */ + int startfrom; + } keyword; + } bs; + RedisModuleKeySpecFindKeysType find_keys_type; + union { + struct { + /* Index of the last key relative to the result of the begin search + * step. Can be negative, in which case it's not relative. -1 + * indicating till the last argument, -2 one before the last and so + * on. */ + int lastkey; + /* How many args should we skip after finding a key, in order to + * find the next one. */ + int keystep; + /* If lastkey is -1, we use limit to stop the search by a factor. 0 + * and 1 mean no limit. 2 means 1/2 of the remaining args, 3 means + * 1/3, and so on. */ + int limit; + } range; + struct { + /* Index of the argument containing the number of keys to come + * relative to the result of the begin search step */ + int keynumidx; + /* Index of the fist key. (Usually it's just after keynumidx, in + * which case it should be set to keynumidx + 1.) */ + int firstkey; + /* How many args should we skip after finding a key, in order to + * find the next one, relative to the result of the begin search + * step. */ + int keystep; + } keynum; + } fk; +} RedisModuleCommandKeySpec; + +typedef struct { + int version; + size_t sizeof_historyentry; + size_t sizeof_keyspec; + size_t sizeof_arg; +} RedisModuleCommandInfoVersion; + +static const RedisModuleCommandInfoVersion RedisModule_CurrentCommandInfoVersion = { + .version = 1, + .sizeof_historyentry = sizeof(RedisModuleCommandHistoryEntry), + .sizeof_keyspec = sizeof(RedisModuleCommandKeySpec), + .sizeof_arg = sizeof(RedisModuleCommandArg) +}; + +#define REDISMODULE_COMMAND_INFO_VERSION (&RedisModule_CurrentCommandInfoVersion) + +typedef struct { + /* Always set version to REDISMODULE_COMMAND_INFO_VERSION */ + const RedisModuleCommandInfoVersion *version; + /* Version 1 fields (added in Redis 7.0.0) */ + const char *summary; /* Summary of the command */ + const char *complexity; /* Complexity description */ + const char *since; /* Debut module version of the command */ + RedisModuleCommandHistoryEntry *history; /* History */ + /* A string of space-separated tips meant for clients/proxies regarding this + * command */ + const char *tips; + /* Number of arguments, it is possible to use -N to say >= N */ + int arity; + RedisModuleCommandKeySpec *key_specs; + RedisModuleCommandArg *args; +} RedisModuleCommandInfo; + +/* Eventloop definitions. */ +#define REDISMODULE_EVENTLOOP_READABLE 1 +#define REDISMODULE_EVENTLOOP_WRITABLE 2 +typedef void (*RedisModuleEventLoopFunc)(int fd, void *user_data, int mask); +typedef void (*RedisModuleEventLoopOneShotFunc)(void *user_data); + /* Server events definitions. * Those flags should not be used directly by the module, instead - * the module should use RedisModuleEvent_* variables */ + * the module should use RedisModuleEvent_* variables. + * Note: This must be synced with moduleEventVersions */ #define REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED 0 #define REDISMODULE_EVENT_PERSISTENCE 1 #define REDISMODULE_EVENT_FLUSHDB 2 @@ -237,9 +437,12 @@ typedef uint64_t RedisModuleTimerID; #define REDISMODULE_EVENT_MODULE_CHANGE 9 #define REDISMODULE_EVENT_LOADING_PROGRESS 10 #define REDISMODULE_EVENT_SWAPDB 11 -#define REDISMODULE_EVENT_REPL_BACKUP 12 +#define REDISMODULE_EVENT_REPL_BACKUP 12 /* Deprecated since Redis 7.0, not used anymore. */ #define REDISMODULE_EVENT_FORK_CHILD 13 -#define _REDISMODULE_EVENT_NEXT 14 /* Next event flag, should be updated if a new event added. */ +#define REDISMODULE_EVENT_REPL_ASYNC_LOAD 14 +#define REDISMODULE_EVENT_EVENTLOOP 15 +#define REDISMODULE_EVENT_CONFIG 16 +#define _REDISMODULE_EVENT_NEXT 17 /* Next event flag, should be updated if a new event added. */ typedef struct RedisModuleEvent { uint64_t id; /* REDISMODULE_EVENT_... defines. */ @@ -250,6 +453,32 @@ struct RedisModuleCtx; struct RedisModuleDefragCtx; typedef void (*RedisModuleEventCallback)(struct RedisModuleCtx *ctx, RedisModuleEvent eid, uint64_t subevent, void *data); +/* IMPORTANT: When adding a new version of one of below structures that contain + * event data (RedisModuleFlushInfoV1 for example) we have to avoid renaming the + * old RedisModuleEvent structure. + * For example, if we want to add RedisModuleFlushInfoV2, the RedisModuleEvent + * structures should be: + * RedisModuleEvent_FlushDB = { + * REDISMODULE_EVENT_FLUSHDB, + * 1 + * }, + * RedisModuleEvent_FlushDBV2 = { + * REDISMODULE_EVENT_FLUSHDB, + * 2 + * } + * and NOT: + * RedisModuleEvent_FlushDBV1 = { + * REDISMODULE_EVENT_FLUSHDB, + * 1 + * }, + * RedisModuleEvent_FlushDB = { + * REDISMODULE_EVENT_FLUSHDB, + * 2 + * } + * The reason for that is forward-compatibility: We want that module that + * compiled with a new redismodule.h to be able to work with a old server, + * unless the author explicitly decided to use the newer event type. + */ static const RedisModuleEvent RedisModuleEvent_ReplicationRoleChanged = { REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED, @@ -299,13 +528,27 @@ static const RedisModuleEvent REDISMODULE_EVENT_SWAPDB, 1 }, + /* Deprecated since Redis 7.0, not used anymore. */ + __attribute__ ((deprecated)) RedisModuleEvent_ReplBackup = { - REDISMODULE_EVENT_REPL_BACKUP, + REDISMODULE_EVENT_REPL_BACKUP, + 1 + }, + RedisModuleEvent_ReplAsyncLoad = { + REDISMODULE_EVENT_REPL_ASYNC_LOAD, 1 }, RedisModuleEvent_ForkChild = { REDISMODULE_EVENT_FORK_CHILD, 1 + }, + RedisModuleEvent_EventLoop = { + REDISMODULE_EVENT_EVENTLOOP, + 1 + }, + RedisModuleEvent_Config = { + REDISMODULE_EVENT_CONFIG, + 1 }; /* Those are values that are used for the 'subevent' callback argument. */ @@ -314,7 +557,8 @@ static const RedisModuleEvent #define REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START 2 #define REDISMODULE_SUBEVENT_PERSISTENCE_ENDED 3 #define REDISMODULE_SUBEVENT_PERSISTENCE_FAILED 4 -#define _REDISMODULE_SUBEVENT_PERSISTENCE_NEXT 5 +#define REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_AOF_START 5 +#define _REDISMODULE_SUBEVENT_PERSISTENCE_NEXT 6 #define REDISMODULE_SUBEVENT_LOADING_RDB_START 0 #define REDISMODULE_SUBEVENT_LOADING_AOF_START 1 @@ -347,19 +591,32 @@ static const RedisModuleEvent #define REDISMODULE_SUBEVENT_MODULE_UNLOADED 1 #define _REDISMODULE_SUBEVENT_MODULE_NEXT 2 +#define REDISMODULE_SUBEVENT_CONFIG_CHANGE 0 +#define _REDISMODULE_SUBEVENT_CONFIG_NEXT 1 + #define REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB 0 #define REDISMODULE_SUBEVENT_LOADING_PROGRESS_AOF 1 #define _REDISMODULE_SUBEVENT_LOADING_PROGRESS_NEXT 2 +/* Replication Backup events are deprecated since Redis 7.0 and are never fired. */ #define REDISMODULE_SUBEVENT_REPL_BACKUP_CREATE 0 #define REDISMODULE_SUBEVENT_REPL_BACKUP_RESTORE 1 #define REDISMODULE_SUBEVENT_REPL_BACKUP_DISCARD 2 #define _REDISMODULE_SUBEVENT_REPL_BACKUP_NEXT 3 +#define REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_STARTED 0 +#define REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_ABORTED 1 +#define REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_COMPLETED 2 +#define _REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_NEXT 3 + #define REDISMODULE_SUBEVENT_FORK_CHILD_BORN 0 #define REDISMODULE_SUBEVENT_FORK_CHILD_DIED 1 #define _REDISMODULE_SUBEVENT_FORK_CHILD_NEXT 2 +#define REDISMODULE_SUBEVENT_EVENTLOOP_BEFORE_SLEEP 0 +#define REDISMODULE_SUBEVENT_EVENTLOOP_AFTER_SLEEP 1 +#define _REDISMODULE_SUBEVENT_EVENTLOOP_NEXT 2 + #define _REDISMODULE_SUBEVENT_SHUTDOWN_NEXT 0 #define _REDISMODULE_SUBEVENT_CRON_LOOP_NEXT 0 #define _REDISMODULE_SUBEVENT_SWAPDB_NEXT 0 @@ -437,6 +694,17 @@ typedef struct RedisModuleModuleChange { #define RedisModuleModuleChange RedisModuleModuleChangeV1 +#define REDISMODULE_CONFIGCHANGE_VERSION 1 +typedef struct RedisModuleConfigChange { + uint64_t version; /* Not used since this structure is never passed + from the module to the core right now. Here + for future compatibility. */ + uint32_t num_changes; /* how many redis config options were changed */ + const char **config_names; /* the config names that were changed */ +} RedisModuleConfigChangeV1; + +#define RedisModuleConfigChange RedisModuleConfigChangeV1 + #define REDISMODULE_CRON_LOOP_VERSION 1 typedef struct RedisModuleCronLoopInfo { uint64_t version; /* Not used since this structure is never passed @@ -470,6 +738,13 @@ typedef struct RedisModuleSwapDbInfo { #define RedisModuleSwapDbInfo RedisModuleSwapDbInfoV1 +typedef enum { + REDISMODULE_ACL_LOG_AUTH = 0, /* Authentication failure */ + REDISMODULE_ACL_LOG_CMD, /* Command authorization failure */ + REDISMODULE_ACL_LOG_KEY, /* Key authorization failure */ + REDISMODULE_ACL_LOG_CHANNEL /* Channel authorization failure */ +} RedisModuleACLLogEntryReason; + /* ------------------------- End of common defines ------------------------ */ #ifndef REDISMODULE_CORE @@ -494,7 +769,7 @@ typedef long long mstime_t; #endif #ifndef REDISMODULE_ATTR_COMMON -# if defined(__GNUC__) && !defined(__clang__) +# if defined(__GNUC__) && !(defined(__clang__) && defined(__cplusplus)) # define REDISMODULE_ATTR_COMMON __attribute__((__common__)) # else # define REDISMODULE_ATTR_COMMON @@ -503,6 +778,7 @@ typedef long long mstime_t; /* Incomplete structures for compiler checks but opaque access. */ typedef struct RedisModuleCtx RedisModuleCtx; +typedef struct RedisModuleCommand RedisModuleCommand; typedef struct RedisModuleKey RedisModuleKey; typedef struct RedisModuleString RedisModuleString; typedef struct RedisModuleCallReply RedisModuleCallReply; @@ -520,6 +796,7 @@ typedef struct RedisModuleServerInfoData RedisModuleServerInfoData; typedef struct RedisModuleScanCursor RedisModuleScanCursor; typedef struct RedisModuleDefragCtx RedisModuleDefragCtx; typedef struct RedisModuleUser RedisModuleUser; +typedef struct RedisModuleKeyOptCtx RedisModuleKeyOptCtx; typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc); @@ -530,11 +807,15 @@ typedef int (*RedisModuleTypeAuxLoadFunc)(RedisModuleIO *rdb, int encver, int wh typedef void (*RedisModuleTypeAuxSaveFunc)(RedisModuleIO *rdb, int when); typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value); typedef size_t (*RedisModuleTypeMemUsageFunc)(const void *value); +typedef size_t (*RedisModuleTypeMemUsageFunc2)(RedisModuleKeyOptCtx *ctx, const void *value, size_t sample_size); typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value); typedef void (*RedisModuleTypeFreeFunc)(void *value); typedef size_t (*RedisModuleTypeFreeEffortFunc)(RedisModuleString *key, const void *value); +typedef size_t (*RedisModuleTypeFreeEffortFunc2)(RedisModuleKeyOptCtx *ctx, const void *value); typedef void (*RedisModuleTypeUnlinkFunc)(RedisModuleString *key, const void *value); +typedef void (*RedisModuleTypeUnlinkFunc2)(RedisModuleKeyOptCtx *ctx, const void *value); typedef void *(*RedisModuleTypeCopyFunc)(RedisModuleString *fromkey, RedisModuleString *tokey, const void *value); +typedef void *(*RedisModuleTypeCopyFunc2)(RedisModuleKeyOptCtx *ctx, const void *value); typedef int (*RedisModuleTypeDefragFunc)(RedisModuleDefragCtx *ctx, RedisModuleString *key, void **value); typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len); typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data); @@ -545,6 +826,15 @@ typedef void (*RedisModuleScanCB)(RedisModuleCtx *ctx, RedisModuleString *keynam typedef void (*RedisModuleScanKeyCB)(RedisModuleKey *key, RedisModuleString *field, RedisModuleString *value, void *privdata); typedef void (*RedisModuleUserChangedFunc) (uint64_t client_id, void *privdata); typedef int (*RedisModuleDefragFunc)(RedisModuleDefragCtx *ctx); +typedef RedisModuleString * (*RedisModuleConfigGetStringFunc)(const char *name, void *privdata); +typedef long long (*RedisModuleConfigGetNumericFunc)(const char *name, void *privdata); +typedef int (*RedisModuleConfigGetBoolFunc)(const char *name, void *privdata); +typedef int (*RedisModuleConfigGetEnumFunc)(const char *name, void *privdata); +typedef int (*RedisModuleConfigSetStringFunc)(const char *name, RedisModuleString *val, void *privdata, RedisModuleString **err); +typedef int (*RedisModuleConfigSetNumericFunc)(const char *name, long long val, void *privdata, RedisModuleString **err); +typedef int (*RedisModuleConfigSetBoolFunc)(const char *name, int val, void *privdata, RedisModuleString **err); +typedef int (*RedisModuleConfigSetEnumFunc)(const char *name, int val, void *privdata, RedisModuleString **err); +typedef int (*RedisModuleConfigApplyFunc)(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err); typedef struct RedisModuleTypeMethods { uint64_t version; @@ -561,6 +851,10 @@ typedef struct RedisModuleTypeMethods { RedisModuleTypeUnlinkFunc unlink; RedisModuleTypeCopyFunc copy; RedisModuleTypeDefragFunc defrag; + RedisModuleTypeMemUsageFunc2 mem_usage2; + RedisModuleTypeFreeEffortFunc2 free_effort2; + RedisModuleTypeUnlinkFunc2 unlink2; + RedisModuleTypeCopyFunc2 copy2; } RedisModuleTypeMethods; #define REDISMODULE_GET_API(name) \ @@ -577,29 +871,46 @@ typedef struct RedisModuleTypeMethods { #endif REDISMODULE_API void * (*RedisModule_Alloc)(size_t bytes) REDISMODULE_ATTR; +REDISMODULE_API void * (*RedisModule_TryAlloc)(size_t bytes) REDISMODULE_ATTR; REDISMODULE_API void * (*RedisModule_Realloc)(void *ptr, size_t bytes) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_Free)(void *ptr) REDISMODULE_ATTR; REDISMODULE_API void * (*RedisModule_Calloc)(size_t nmemb, size_t size) REDISMODULE_ATTR; REDISMODULE_API char * (*RedisModule_Strdup)(const char *str) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_GetApi)(const char *, void *) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleCommand *(*RedisModule_GetCommand)(RedisModuleCtx *ctx, const char *name) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_CreateSubcommand)(RedisModuleCommand *parent, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SetCommandInfo)(RedisModuleCommand *command, const RedisModuleCommandInfo *info) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_IsModuleNameBusy)(const char *name) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_WrongArity)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_GetSelectedDb)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid) REDISMODULE_ATTR; -REDISMODULE_API void * (*RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_KeyExists)(RedisModuleCtx *ctx, RedisModuleString *keyname) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleKey * (*RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_CloseKey)(RedisModuleKey *kp) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_KeyType)(RedisModuleKey *kp) REDISMODULE_ATTR; REDISMODULE_API size_t (*RedisModule_ValueLength)(RedisModuleKey *kp) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_ListPop)(RedisModuleKey *key, int where) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_ListGet)(RedisModuleKey *key, long index) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ListSet)(RedisModuleKey *key, long index, RedisModuleString *value) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ListInsert)(RedisModuleKey *key, long index, RedisModuleString *value) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ListDelete)(RedisModuleKey *key, long index) REDISMODULE_ATTR; REDISMODULE_API RedisModuleCallReply * (*RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) REDISMODULE_ATTR; REDISMODULE_API const char * (*RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_FreeCallReply)(RedisModuleCallReply *reply) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_CallReplyType)(RedisModuleCallReply *reply) REDISMODULE_ATTR; REDISMODULE_API long long (*RedisModule_CallReplyInteger)(RedisModuleCallReply *reply) REDISMODULE_ATTR; +REDISMODULE_API double (*RedisModule_CallReplyDouble)(RedisModuleCallReply *reply) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_CallReplyBool)(RedisModuleCallReply *reply) REDISMODULE_ATTR; +REDISMODULE_API const char* (*RedisModule_CallReplyBigNumber)(RedisModuleCallReply *reply, size_t *len) REDISMODULE_ATTR; +REDISMODULE_API const char* (*RedisModule_CallReplyVerbatim)(RedisModuleCallReply *reply, size_t *len, const char **format) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleCallReply * (*RedisModule_CallReplySetElement)(RedisModuleCallReply *reply, size_t idx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_CallReplyMapElement)(RedisModuleCallReply *reply, size_t idx, RedisModuleCallReply **key, RedisModuleCallReply **val) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_CallReplyAttributeElement)(RedisModuleCallReply *reply, size_t idx, RedisModuleCallReply **key, RedisModuleCallReply **val) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleCallReply * (*RedisModule_CallReplyAttribute)(RedisModuleCallReply *reply) REDISMODULE_ATTR; REDISMODULE_API size_t (*RedisModule_CallReplyLength)(RedisModuleCallReply *reply) REDISMODULE_ATTR; REDISMODULE_API RedisModuleCallReply * (*RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len) REDISMODULE_ATTR; @@ -616,17 +927,27 @@ REDISMODULE_API const char * (*RedisModule_StringPtrLen)(const RedisModuleString REDISMODULE_API int (*RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithMap)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithSet)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithAttribute)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithNullArray)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithEmptyArray)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ReplySetMapLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ReplySetSetLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ReplySetAttributeLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ReplySetPushLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithEmptyString)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithVerbatimString)(RedisModuleCtx *ctx, const char *buf, size_t len) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithVerbatimStringType)(RedisModuleCtx *ctx, const char *buf, size_t len, const char *ext) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithNull)(RedisModuleCtx *ctx) REDISMODULE_ATTR; -REDISMODULE_API int (*RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithBool)(RedisModuleCtx *ctx, int b) REDISMODULE_ATTR; // REDISMODULE_API int (*RedisModule_ReplyWithLongDouble)(RedisModuleCtx *ctx, long double d) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithBigNumber)(RedisModuleCtx *ctx, const char *bignum, size_t len) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_StringToDouble)(const RedisModuleString *str, double *d) REDISMODULE_ATTR; @@ -675,10 +996,14 @@ REDISMODULE_API long long (*RedisModule_StreamTrimByLength)(RedisModuleKey *key, REDISMODULE_API long long (*RedisModule_StreamTrimByID)(RedisModuleKey *key, int flags, RedisModuleStreamID *id) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_KeyAtPosWithFlags)(RedisModuleCtx *ctx, int pos, int flags) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_IsChannelsPositionRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ChannelAtPosWithFlags)(RedisModuleCtx *ctx, int pos, int flags) REDISMODULE_ATTR; REDISMODULE_API unsigned long long (*RedisModule_GetClientId)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_GetClientUserNameById)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_GetClientInfoById)(void *ci, uint64_t id) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_PublishMessage)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_PublishMessageShard)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_GetContextFlags)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_AvoidReplicaTraffic)() REDISMODULE_ATTR; REDISMODULE_API void * (*RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes) REDISMODULE_ATTR; @@ -706,22 +1031,33 @@ REDISMODULE_API float (*RedisModule_LoadFloat)(RedisModuleIO *io) REDISMODULE_AT // REDISMODULE_API void (*RedisModule_SaveLongDouble)(RedisModuleIO *io, long double value) REDISMODULE_ATTR; // REDISMODULE_API long double (*RedisModule_LoadLongDouble)(RedisModuleIO *io) REDISMODULE_ATTR; REDISMODULE_API void * (*RedisModule_LoadDataTypeFromString)(const RedisModuleString *str, const RedisModuleType *mt) REDISMODULE_ATTR; +REDISMODULE_API void * (*RedisModule_LoadDataTypeFromStringEncver)(const RedisModuleString *str, const RedisModuleType *mt, int encver) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_SaveDataTypeToString)(RedisModuleCtx *ctx, void *data, const RedisModuleType *mt) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...) REDISMODULE_ATTR REDISMODULE_ATTR_PRINTF(3,4); REDISMODULE_API void (*RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...) REDISMODULE_ATTR REDISMODULE_ATTR_PRINTF(3,4); REDISMODULE_API void (*RedisModule__Assert)(const char *estr, const char *file, int line) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_LatencyAddSample)(const char *event, mstime_t latency) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_TrimStringAllocation)(RedisModuleString *str) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_HoldString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_StringCompare)(const RedisModuleString *a, const RedisModuleString *b) REDISMODULE_ATTR; REDISMODULE_API RedisModuleCtx * (*RedisModule_GetContextFromIO)(RedisModuleIO *io) REDISMODULE_ATTR; REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromIO)(RedisModuleIO *io) REDISMODULE_ATTR; REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromModuleKey)(RedisModuleKey *key) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetDbIdFromModuleKey)(RedisModuleKey *key) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetDbIdFromIO)(RedisModuleIO *io) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetDbIdFromOptCtx)(RedisModuleKeyOptCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetToDbIdFromOptCtx)(RedisModuleKeyOptCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromOptCtx)(RedisModuleKeyOptCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API const RedisModuleString * (*RedisModule_GetToKeyNameFromOptCtx)(RedisModuleKeyOptCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API long long (*RedisModule_Milliseconds)(void) REDISMODULE_ATTR; -REDISMODULE_API void (*RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len) REDISMODULE_ATTR; +REDISMODULE_API uint64_t (*RedisModule_MonotonicMicroseconds)(void) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, const char *ele, size_t len) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_DigestEndSequence)(RedisModuleDigest *md) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetDbIdFromDigest)(RedisModuleDigest *dig) REDISMODULE_ATTR; +REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromDigest)(RedisModuleDigest *dig) REDISMODULE_ATTR; REDISMODULE_API RedisModuleDict * (*RedisModule_CreateDict)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_FreeDict)(RedisModuleCtx *ctx, RedisModuleDict *d) REDISMODULE_ATTR; REDISMODULE_API uint64_t (*RedisModule_DictSize)(RedisModuleDict *d) REDISMODULE_ATTR; @@ -745,14 +1081,14 @@ REDISMODULE_API RedisModuleString * (*RedisModule_DictPrev)(RedisModuleCtx *ctx, REDISMODULE_API int (*RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_RegisterInfoFunc)(RedisModuleCtx *ctx, RedisModuleInfoFunc cb) REDISMODULE_ATTR; -REDISMODULE_API int (*RedisModule_InfoAddSection)(RedisModuleInfoCtx *ctx, char *name) REDISMODULE_ATTR; -REDISMODULE_API int (*RedisModule_InfoBeginDictField)(RedisModuleInfoCtx *ctx, char *name) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_InfoAddSection)(RedisModuleInfoCtx *ctx, const char *name) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_InfoBeginDictField)(RedisModuleInfoCtx *ctx, const char *name) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_InfoEndDictField)(RedisModuleInfoCtx *ctx) REDISMODULE_ATTR; -REDISMODULE_API int (*RedisModule_InfoAddFieldString)(RedisModuleInfoCtx *ctx, char *field, RedisModuleString *value) REDISMODULE_ATTR; -REDISMODULE_API int (*RedisModule_InfoAddFieldCString)(RedisModuleInfoCtx *ctx, char *field, char *value) REDISMODULE_ATTR; -REDISMODULE_API int (*RedisModule_InfoAddFieldDouble)(RedisModuleInfoCtx *ctx, char *field, double value) REDISMODULE_ATTR; -REDISMODULE_API int (*RedisModule_InfoAddFieldLongLong)(RedisModuleInfoCtx *ctx, char *field, long long value) REDISMODULE_ATTR; -REDISMODULE_API int (*RedisModule_InfoAddFieldULongLong)(RedisModuleInfoCtx *ctx, char *field, unsigned long long value) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_InfoAddFieldString)(RedisModuleInfoCtx *ctx, const char *field, RedisModuleString *value) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_InfoAddFieldCString)(RedisModuleInfoCtx *ctx, const char *field,const char *value) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_InfoAddFieldDouble)(RedisModuleInfoCtx *ctx, const char *field, double value) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_InfoAddFieldLongLong)(RedisModuleInfoCtx *ctx, const char *field, long long value) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_InfoAddFieldULongLong)(RedisModuleInfoCtx *ctx, const char *field, unsigned long long value) REDISMODULE_ATTR; REDISMODULE_API RedisModuleServerInfoData * (*RedisModule_GetServerInfo)(RedisModuleCtx *ctx, const char *section) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_FreeServerInfo)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_ServerInfoGetField)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data, const char* field) REDISMODULE_ATTR; @@ -778,10 +1114,7 @@ REDISMODULE_API int (*RedisModule_GetKeyspaceNotificationFlagsAll)() REDISMODULE REDISMODULE_API int (*RedisModule_IsSubEventSupported)(RedisModuleEvent event, uint64_t subevent) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_GetServerVersion)() REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_GetTypeMethodVersion)() REDISMODULE_ATTR; - -/* Experimental APIs */ -#ifdef REDISMODULE_EXPERIMENTAL_API -#define REDISMODULE_EXPERIMENTAL_API_VERSION 3 +REDISMODULE_API void (*RedisModule_Yield)(RedisModuleCtx *ctx, int flags, const char *busy_reply) REDISMODULE_ATTR; REDISMODULE_API RedisModuleBlockedClient * (*RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_UnblockClient)(RedisModuleBlockedClient *bc, void *privdata) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_IsBlockedReplyRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR; @@ -802,7 +1135,7 @@ REDISMODULE_API int (*RedisModule_NotifyKeyspaceEvent)(RedisModuleCtx *ctx, int REDISMODULE_API int (*RedisModule_GetNotifyKeyspaceEvents)() REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_BlockedClientDisconnected)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_RegisterClusterMessageReceiver)(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback) REDISMODULE_ATTR; -REDISMODULE_API int (*RedisModule_SendClusterMessage)(RedisModuleCtx *ctx, char *target_id, uint8_t type, unsigned char *msg, uint32_t len) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SendClusterMessage)(RedisModuleCtx *ctx, const char *target_id, uint8_t type, const char *msg, uint32_t len) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_GetClusterNodeInfo)(RedisModuleCtx *ctx, const char *id, char *ip, char *master_id, int *port, int *flags) REDISMODULE_ATTR; REDISMODULE_API char ** (*RedisModule_GetClusterNodesList)(RedisModuleCtx *ctx, size_t *numnodes) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_FreeClusterNodesList)(char **ids) REDISMODULE_ATTR; @@ -820,7 +1153,7 @@ REDISMODULE_API void * (*RedisModule_GetSharedAPI)(RedisModuleCtx *ctx, const ch REDISMODULE_API RedisModuleCommandFilter * (*RedisModule_RegisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc cb, int flags) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_UnregisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_CommandFilterArgsCount)(RedisModuleCommandFilterCtx *fctx) REDISMODULE_ATTR; -REDISMODULE_API const RedisModuleString * (*RedisModule_CommandFilterArgGet)(RedisModuleCommandFilterCtx *fctx, int pos) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_CommandFilterArgGet)(RedisModuleCommandFilterCtx *fctx, int pos) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos) REDISMODULE_ATTR; @@ -830,14 +1163,25 @@ REDISMODULE_API int (*RedisModule_ExitFromChild)(int retcode) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_KillForkChild)(int child_pid) REDISMODULE_ATTR; REDISMODULE_API float (*RedisModule_GetUsedMemoryRatio)() REDISMODULE_ATTR; REDISMODULE_API size_t (*RedisModule_MallocSize)(void* ptr) REDISMODULE_ATTR; +REDISMODULE_API size_t (*RedisModule_MallocUsableSize)(void *ptr) REDISMODULE_ATTR; +REDISMODULE_API size_t (*RedisModule_MallocSizeString)(RedisModuleString* str) REDISMODULE_ATTR; +REDISMODULE_API size_t (*RedisModule_MallocSizeDict)(RedisModuleDict* dict) REDISMODULE_ATTR; REDISMODULE_API RedisModuleUser * (*RedisModule_CreateModuleUser)(const char *name) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_FreeModuleUser)(RedisModuleUser *user) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_SetModuleUserACL)(RedisModuleUser *user, const char* acl) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_GetCurrentUserName)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleUser * (*RedisModule_GetModuleUserFromUserName)(RedisModuleString *name) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ACLCheckCommandPermissions)(RedisModuleUser *user, RedisModuleString **argv, int argc) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ACLCheckKeyPermissions)(RedisModuleUser *user, RedisModuleString *key, int flags) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ACLCheckChannelPermissions)(RedisModuleUser *user, RedisModuleString *ch, int literal) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ACLAddLogEntry)(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleString *object, RedisModuleACLLogEntryReason reason) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_AuthenticateClientWithACLUser)(RedisModuleCtx *ctx, const char *name, size_t len, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_RedactClientCommandArgument)(RedisModuleCtx *ctx, int pos) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_GetClientCertificate)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR; REDISMODULE_API int *(*RedisModule_GetCommandKeys)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys) REDISMODULE_ATTR; +REDISMODULE_API int *(*RedisModule_GetCommandKeysWithFlags)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys, int **out_flags) REDISMODULE_ATTR; REDISMODULE_API const char *(*RedisModule_GetCurrentCommandName)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_RegisterDefragFunc)(RedisModuleCtx *ctx, RedisModuleDefragFunc func) REDISMODULE_ATTR; REDISMODULE_API void *(*RedisModule_DefragAlloc)(RedisModuleDefragCtx *ctx, void *ptr) REDISMODULE_ATTR; @@ -845,7 +1189,16 @@ REDISMODULE_API RedisModuleString *(*RedisModule_DefragRedisModuleString)(RedisM REDISMODULE_API int (*RedisModule_DefragShouldStop)(RedisModuleDefragCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_DefragCursorSet)(RedisModuleDefragCtx *ctx, unsigned long cursor) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_DefragCursorGet)(RedisModuleDefragCtx *ctx, unsigned long *cursor) REDISMODULE_ATTR; -#endif +REDISMODULE_API int (*RedisModule_GetDbIdFromDefragCtx)(RedisModuleDefragCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromDefragCtx)(RedisModuleDefragCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_EventLoopAdd)(int fd, int mask, RedisModuleEventLoopFunc func, void *user_data) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_EventLoopDel)(int fd, int mask) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_EventLoopAddOneShot)(RedisModuleEventLoopOneShotFunc func, void *user_data) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_RegisterBoolConfig)(RedisModuleCtx *ctx, const char *name, int default_val, unsigned int flags, RedisModuleConfigGetBoolFunc getfn, RedisModuleConfigSetBoolFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_RegisterNumericConfig)(RedisModuleCtx *ctx, const char *name, long long default_val, unsigned int flags, long long min, long long max, RedisModuleConfigGetNumericFunc getfn, RedisModuleConfigSetNumericFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_RegisterStringConfig)(RedisModuleCtx *ctx, const char *name, const char *default_val, unsigned int flags, RedisModuleConfigGetStringFunc getfn, RedisModuleConfigSetStringFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_RegisterEnumConfig)(RedisModuleCtx *ctx, const char *name, int default_val, unsigned int flags, const char **enum_values, const int *int_values, int num_enum_vals, RedisModuleConfigGetEnumFunc getfn, RedisModuleConfigSetEnumFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_LoadConfigs)(RedisModuleCtx *ctx) REDISMODULE_ATTR; #define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX) @@ -855,11 +1208,15 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int void *getapifuncptr = ((void**)ctx)[0]; RedisModule_GetApi = (int (*)(const char *, void *)) (unsigned long)getapifuncptr; REDISMODULE_GET_API(Alloc); + REDISMODULE_GET_API(TryAlloc); REDISMODULE_GET_API(Calloc); REDISMODULE_GET_API(Free); REDISMODULE_GET_API(Realloc); REDISMODULE_GET_API(Strdup); REDISMODULE_GET_API(CreateCommand); + REDISMODULE_GET_API(GetCommand); + REDISMODULE_GET_API(CreateSubcommand); + REDISMODULE_GET_API(SetCommandInfo); REDISMODULE_GET_API(SetModuleAttribs); REDISMODULE_GET_API(IsModuleNameBusy); REDISMODULE_GET_API(WrongArity); @@ -867,26 +1224,41 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(ReplyWithError); REDISMODULE_GET_API(ReplyWithSimpleString); REDISMODULE_GET_API(ReplyWithArray); + REDISMODULE_GET_API(ReplyWithMap); + REDISMODULE_GET_API(ReplyWithSet); + REDISMODULE_GET_API(ReplyWithAttribute); REDISMODULE_GET_API(ReplyWithNullArray); REDISMODULE_GET_API(ReplyWithEmptyArray); REDISMODULE_GET_API(ReplySetArrayLength); + REDISMODULE_GET_API(ReplySetMapLength); + REDISMODULE_GET_API(ReplySetSetLength); + REDISMODULE_GET_API(ReplySetAttributeLength); + REDISMODULE_GET_API(ReplySetPushLength); REDISMODULE_GET_API(ReplyWithStringBuffer); REDISMODULE_GET_API(ReplyWithCString); REDISMODULE_GET_API(ReplyWithString); REDISMODULE_GET_API(ReplyWithEmptyString); REDISMODULE_GET_API(ReplyWithVerbatimString); + REDISMODULE_GET_API(ReplyWithVerbatimStringType); REDISMODULE_GET_API(ReplyWithNull); + REDISMODULE_GET_API(ReplyWithBool); REDISMODULE_GET_API(ReplyWithCallReply); REDISMODULE_GET_API(ReplyWithDouble); + REDISMODULE_GET_API(ReplyWithBigNumber); // REDISMODULE_GET_API(ReplyWithLongDouble); REDISMODULE_GET_API(GetSelectedDb); REDISMODULE_GET_API(SelectDb); + REDISMODULE_GET_API(KeyExists); REDISMODULE_GET_API(OpenKey); REDISMODULE_GET_API(CloseKey); REDISMODULE_GET_API(KeyType); REDISMODULE_GET_API(ValueLength); REDISMODULE_GET_API(ListPush); REDISMODULE_GET_API(ListPop); + REDISMODULE_GET_API(ListGet); + REDISMODULE_GET_API(ListSet); + REDISMODULE_GET_API(ListInsert); + REDISMODULE_GET_API(ListDelete); REDISMODULE_GET_API(StringToLongLong); REDISMODULE_GET_API(StringToDouble); // REDISMODULE_GET_API(StringToLongDouble); @@ -895,6 +1267,14 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(CallReplyProto); REDISMODULE_GET_API(FreeCallReply); REDISMODULE_GET_API(CallReplyInteger); + REDISMODULE_GET_API(CallReplyDouble); + REDISMODULE_GET_API(CallReplyBool); + REDISMODULE_GET_API(CallReplyBigNumber); + REDISMODULE_GET_API(CallReplyVerbatim); + REDISMODULE_GET_API(CallReplySetElement); + REDISMODULE_GET_API(CallReplyMapElement); + REDISMODULE_GET_API(CallReplyAttributeElement); + REDISMODULE_GET_API(CallReplyAttribute); REDISMODULE_GET_API(CallReplyType); REDISMODULE_GET_API(CallReplyLength); REDISMODULE_GET_API(CallReplyArrayElement); @@ -950,6 +1330,9 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(StreamTrimByID); REDISMODULE_GET_API(IsKeysPositionRequest); REDISMODULE_GET_API(KeyAtPos); + REDISMODULE_GET_API(KeyAtPosWithFlags); + REDISMODULE_GET_API(IsChannelsPositionRequest); + REDISMODULE_GET_API(ChannelAtPosWithFlags); REDISMODULE_GET_API(GetClientId); REDISMODULE_GET_API(GetClientUserNameById); REDISMODULE_GET_API(GetContextFlags); @@ -979,22 +1362,33 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int // REDISMODULE_GET_API(LoadLongDouble); REDISMODULE_GET_API(SaveDataTypeToString); REDISMODULE_GET_API(LoadDataTypeFromString); + REDISMODULE_GET_API(LoadDataTypeFromStringEncver); REDISMODULE_GET_API(EmitAOF); REDISMODULE_GET_API(Log); REDISMODULE_GET_API(LogIOError); REDISMODULE_GET_API(_Assert); REDISMODULE_GET_API(LatencyAddSample); REDISMODULE_GET_API(StringAppendBuffer); + REDISMODULE_GET_API(TrimStringAllocation); REDISMODULE_GET_API(RetainString); REDISMODULE_GET_API(HoldString); REDISMODULE_GET_API(StringCompare); REDISMODULE_GET_API(GetContextFromIO); REDISMODULE_GET_API(GetKeyNameFromIO); REDISMODULE_GET_API(GetKeyNameFromModuleKey); + REDISMODULE_GET_API(GetDbIdFromModuleKey); + REDISMODULE_GET_API(GetDbIdFromIO); + REDISMODULE_GET_API(GetKeyNameFromOptCtx); + REDISMODULE_GET_API(GetToKeyNameFromOptCtx); + REDISMODULE_GET_API(GetDbIdFromOptCtx); + REDISMODULE_GET_API(GetToDbIdFromOptCtx); REDISMODULE_GET_API(Milliseconds); + REDISMODULE_GET_API(MonotonicMicroseconds); REDISMODULE_GET_API(DigestAddStringBuffer); REDISMODULE_GET_API(DigestAddLongLong); REDISMODULE_GET_API(DigestEndSequence); + REDISMODULE_GET_API(GetKeyNameFromDigest); + REDISMODULE_GET_API(GetDbIdFromDigest); REDISMODULE_GET_API(CreateDict); REDISMODULE_GET_API(FreeDict); REDISMODULE_GET_API(DictSize); @@ -1035,6 +1429,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(ServerInfoGetFieldDouble); REDISMODULE_GET_API(GetClientInfoById); REDISMODULE_GET_API(PublishMessage); + REDISMODULE_GET_API(PublishMessageShard); REDISMODULE_GET_API(SubscribeToServerEvent); REDISMODULE_GET_API(SetLRU); REDISMODULE_GET_API(GetLRU); @@ -1053,8 +1448,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(IsSubEventSupported); REDISMODULE_GET_API(GetServerVersion); REDISMODULE_GET_API(GetTypeMethodVersion); - -#ifdef REDISMODULE_EXPERIMENTAL_API + REDISMODULE_GET_API(Yield); REDISMODULE_GET_API(GetThreadSafeContext); REDISMODULE_GET_API(GetDetachedThreadSafeContext); REDISMODULE_GET_API(FreeThreadSafeContext); @@ -1103,14 +1497,25 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(KillForkChild); REDISMODULE_GET_API(GetUsedMemoryRatio); REDISMODULE_GET_API(MallocSize); + REDISMODULE_GET_API(MallocUsableSize); + REDISMODULE_GET_API(MallocSizeString); + REDISMODULE_GET_API(MallocSizeDict); REDISMODULE_GET_API(CreateModuleUser); REDISMODULE_GET_API(FreeModuleUser); REDISMODULE_GET_API(SetModuleUserACL); + REDISMODULE_GET_API(GetCurrentUserName); + REDISMODULE_GET_API(GetModuleUserFromUserName); + REDISMODULE_GET_API(ACLCheckCommandPermissions); + REDISMODULE_GET_API(ACLCheckKeyPermissions); + REDISMODULE_GET_API(ACLCheckChannelPermissions); + REDISMODULE_GET_API(ACLAddLogEntry); REDISMODULE_GET_API(DeauthenticateAndCloseClient); REDISMODULE_GET_API(AuthenticateClientWithACLUser); REDISMODULE_GET_API(AuthenticateClientWithUser); + REDISMODULE_GET_API(RedactClientCommandArgument); REDISMODULE_GET_API(GetClientCertificate); REDISMODULE_GET_API(GetCommandKeys); + REDISMODULE_GET_API(GetCommandKeysWithFlags); REDISMODULE_GET_API(GetCurrentCommandName); REDISMODULE_GET_API(RegisterDefragFunc); REDISMODULE_GET_API(DefragAlloc); @@ -1118,7 +1523,16 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(DefragShouldStop); REDISMODULE_GET_API(DefragCursorSet); REDISMODULE_GET_API(DefragCursorGet); -#endif + REDISMODULE_GET_API(GetKeyNameFromDefragCtx); + REDISMODULE_GET_API(GetDbIdFromDefragCtx); + REDISMODULE_GET_API(EventLoopAdd); + REDISMODULE_GET_API(EventLoopDel); + REDISMODULE_GET_API(EventLoopAddOneShot); + REDISMODULE_GET_API(RegisterBoolConfig); + REDISMODULE_GET_API(RegisterNumericConfig); + REDISMODULE_GET_API(RegisterStringConfig); + REDISMODULE_GET_API(RegisterEnumConfig); + REDISMODULE_GET_API(LoadConfigs); if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR; RedisModule_SetModuleAttribs(ctx,name,ver,apiver); diff --git a/src/key.rs b/src/key.rs index ef18f9a9..9e4621fb 100644 --- a/src/key.rs +++ b/src/key.rs @@ -12,6 +12,7 @@ use crate::from_byte_string; use crate::native_types::RedisType; use crate::raw; use crate::redismodule::REDIS_OK; +use crate::stream::StreamIterator; use crate::RedisError; use crate::RedisResult; use crate::RedisString; @@ -32,8 +33,8 @@ pub enum KeyMode { #[derive(Debug)] pub struct RedisKey { - ctx: *mut raw::RedisModuleCtx, - key_inner: *mut raw::RedisModuleKey, + pub(crate) ctx: *mut raw::RedisModuleCtx, + pub(crate) key_inner: *mut raw::RedisModuleKey, } impl RedisKey { @@ -116,12 +117,27 @@ impl RedisKey { }; Ok(val) } + + pub fn get_stream_iterator(&self) -> Result { + StreamIterator::new(self, None, None, false) + } + + pub fn get_stream_range_iterator( + &self, + from: Option, + to: Option, + exclusive: bool, + ) -> Result { + StreamIterator::new(self, from, to, exclusive) + } } 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); + } } } @@ -300,9 +316,9 @@ impl RedisKeyWritable { /// # Panics /// - /// Will panic if `RedisModule_ModuleTypeGetValue` is missing in redismodule.h - pub fn get_value<'a, 'b, T>( - &'a self, + /// Will panic if `RedisModule_ModuleTypeGetValue` is missing in redismodule.h + pub fn get_value<'b, T>( + &self, redis_type: &RedisType, ) -> Result, RedisError> { verify_type(self.key_inner, redis_type)?; @@ -334,6 +350,26 @@ impl RedisKeyWritable { status.into() } + + pub fn trim_stream_by_id( + &self, + mut id: raw::RedisModuleStreamID, + approx: bool, + ) -> Result { + let flags = if approx { + raw::REDISMODULE_STREAM_TRIM_APPROX + } else { + 0 + }; + let res = unsafe { + raw::RedisModule_StreamTrimByID.unwrap()(self.key_inner, flags as i32, &mut id) + }; + if res <= 0 { + Err(RedisError::Str("Failed trimming the stream")) + } else { + Ok(res as usize) + } + } } /// Opaque type used to hold multi-get results. Use the provided methods to convert @@ -395,13 +431,13 @@ where /// Provides an iterator over the multi-get results in the form of (field-name, field-value) /// pairs. The type of field-name elements is the same as that passed to the original multi- /// get call, while the field-value elements may be of any type for which a `RedisString` `Into` - /// conversion is implemented. + /// conversion is implemented. /// /// # Examples /// /// Get a [`HashMap`] from the results: /// - /// ``` + /// ```rust,no_run,ignore /// use std::collections::HashMap; /// use redis_module::RedisError; /// @@ -416,7 +452,7 @@ where /// /// Get a [`Vec`] of only the field values from the results: /// - /// ``` + /// ```rust,no_run,ignore /// use redis_module::RedisError; /// /// let hm = ctx diff --git a/src/lib.rs b/src/lib.rs index 61c89025..0652609e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,10 +17,11 @@ mod redismodule; pub mod redisraw; pub mod redisvalue; -mod context; +pub mod context; pub mod key; pub mod logging; mod macros; +pub mod stream; mod utils; #[cfg(feature = "experimental-api")] @@ -35,12 +36,11 @@ pub use crate::raw::*; pub use crate::redismodule::*; use backtrace::Backtrace; -/// Ideally this would be `#[cfg(not(test))]`, but that doesn't work: -/// [59168#issuecomment-472653680](https://github.com/rust-lang/rust/issues/59168#issuecomment-472653680) -/// The workaround is to use the `test` feature instead. -#[cfg(not(feature = "test"))] -#[global_allocator] -static ALLOC: crate::alloc::RedisAlloc = crate::alloc::RedisAlloc; +#[cfg_attr( + not(any(test, feature = "fallback_to_system_allocator")), + global_allocator +)] +pub static ALLOC: crate::alloc::RedisAlloc = crate::alloc::RedisAlloc; /// `LogLevel` is a level of logging to be specified with a Redis log directive. #[derive(Clone, Copy, Debug, AsRefStr)] @@ -53,7 +53,7 @@ pub enum LogLevel { } fn from_byte_string(byte_str: *const c_char, length: size_t) -> Result { - let mut vec_str: Vec = Vec::with_capacity(length as usize); + let mut vec_str: Vec = Vec::with_capacity(length); for j in 0..length { let byte = unsafe { *byte_str.add(j) } as u8; vec_str.insert(j, byte); diff --git a/src/logging.rs b/src/logging.rs index a2726a00..b593b6f8 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -4,9 +4,9 @@ use std::ffi::CString; use std::ptr; pub(crate) fn log_internal(ctx: *mut raw::RedisModuleCtx, level: LogLevel, message: &str) { - if cfg!(feature = "test") { - return; - } + #[cfg(test)] + return; + let level = CString::new(level.as_ref()).unwrap(); let fmt = CString::new(message).unwrap(); unsafe { raw::RedisModule_Log.unwrap()(ctx, level.as_ptr(), fmt.as_ptr()) } diff --git a/src/macros.rs b/src/macros.rs index eacb50b4..36851515 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -57,7 +57,7 @@ macro_rules! redis_event_handler { ) -> c_int { let context = $crate::Context::new(ctx); - let redis_key = $crate::RedisString::from_ptr(key).unwrap(); + let redis_key = $crate::RedisString::string_as_slice(key); let event_str = unsafe { CStr::from_ptr(event) }; $event_handler( &context, @@ -91,6 +91,7 @@ macro_rules! redis_module { $($data_type:ident),* $(,)* ], $(init: $init_func:ident,)* $(,)* + $(post_init: $post_init_func:ident,)* $(,)* $(deinit: $deinit_func:ident,)* $(,)* $(info: $info_func:ident,)? commands: [ @@ -109,6 +110,24 @@ macro_rules! redis_module { $event_handler:expr ]),* $(,)* ])? + $(, server_events: [ + $([ + @$server_event_type:ident: + $server_event_handler:expr + ]),* $(,)* + ])? + $(, string_configurations: [ + $($string_config_ctx:expr),* $(,)* + ])? + $(, bool_configurations: [ + $($bool_config_ctx:expr),* $(,)* + ])? + $(, numeric_configurations: [ + $($numeric_config_ctx:expr),* $(,)* + ])? + $(, enum_configurations: [ + $($enum_config_ctx:expr),* $(,)* + ])? ) => { extern "C" fn __info_func( ctx: *mut $crate::raw::RedisModuleInfoCtx, @@ -132,6 +151,7 @@ macro_rules! redis_module { use $crate::raw; use $crate::RedisString; + use $crate::context::configuration; // We use a statically sized buffer to avoid allocating. // This is needed since we use a custom allocator that relies on the Redis allocator, @@ -179,8 +199,68 @@ macro_rules! redis_module { )* )? + $( + $( + if let Err(err) = ($crate::context::server_events::subscribe_to_server_event( + &context, + $crate::context::server_events::ServerEvents::$server_event_type, + Box::new($server_event_handler))) { + context.log_warning(&format!("Failed register server event, {}", err)); + return $crate::Status::Err as c_int; + } + )* + )? + + if let Some(load_config) = raw::RedisModule_LoadConfigs.as_ref() { + // configuration api only available on Redis 7.0, if its not available + // ignore it. User will have to find another way to read configuration. + $( + $( + if let Err(err) = configuration::register_string_configuration(&context, $string_config_ctx) { + context.log_warning(&format!("Failed register string configuration, {}", err)); + return $crate::Status::Err as c_int; + } + )* + )? + + $( + $( + if let Err(err) = configuration::register_bool_configuration(&context, $bool_config_ctx) { + context.log_warning(&format!("Failed register string configuration, {}", err)); + return $crate::Status::Err as c_int; + } + )* + )? + + $( + $( + if let Err(err) = configuration::register_numeric_configuration(&context, $numeric_config_ctx) { + context.log_warning(&format!("Failed register string configuration, {}", err)); + return $crate::Status::Err as c_int; + } + )* + )? + + $( + $( + if let Err(err) = configuration::register_enum_configuration(&context, $enum_config_ctx) { + context.log_warning(&format!("Failed register string configuration, {}", err)); + return $crate::Status::Err as c_int; + } + )* + )? + + load_config(ctx); + } + raw::register_info_function(ctx, Some(__info_func)); + $( + if $post_init_func(&context, &args) == $crate::Status::Err { + return $crate::Status::Err as c_int; + } + )* + raw::Status::Ok as c_int } diff --git a/src/raw.rs b/src/raw.rs index ae122442..6b0ae411 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -68,6 +68,12 @@ pub enum ReplyType { Integer = REDISMODULE_REPLY_INTEGER, Array = REDISMODULE_REPLY_ARRAY, Null = REDISMODULE_REPLY_NULL, + Map = REDISMODULE_REPLY_MAP, + Set = REDISMODULE_REPLY_SET, + Bool = REDISMODULE_REPLY_BOOL, + Double = REDISMODULE_REPLY_DOUBLE, + BigNumber = REDISMODULE_REPLY_BIG_NUMBER, + VerbatimString = REDISMODULE_REPLY_VERBATIM_STRING, } impl From for ReplyType { @@ -117,6 +123,7 @@ bitflags! { const STREAM = REDISMODULE_NOTIFY_STREAM; const MODULE = REDISMODULE_NOTIFY_MODULE; const LOADED = REDISMODULE_NOTIFY_LOADED; + const MISSED = REDISMODULE_NOTIFY_KEY_MISS; const ALL = REDISMODULE_NOTIFY_ALL; } } @@ -201,6 +208,40 @@ pub fn call_reply_integer(reply: *mut RedisModuleCallReply) -> c_longlong { unsafe { RedisModule_CallReplyInteger.unwrap()(reply) } } +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn call_reply_bool(reply: *mut RedisModuleCallReply) -> c_int { + unsafe { RedisModule_CallReplyBool.unwrap()(reply) } +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn call_reply_double(reply: *mut RedisModuleCallReply) -> f64 { + unsafe { RedisModule_CallReplyDouble.unwrap()(reply) } +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn call_reply_big_numebr(reply: *mut RedisModuleCallReply) -> String { + unsafe { + let mut len: size_t = 0; + let reply_string: *mut u8 = + RedisModule_CallReplyBigNumber.unwrap()(reply, &mut len) as *mut u8; + String::from_utf8(slice::from_raw_parts(reply_string, len).to_vec()).unwrap() + } +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn call_reply_verbatim_string(reply: *mut RedisModuleCallReply) -> (String, String) { + unsafe { + let mut len: size_t = 0; + let mut format: *const c_char = ptr::null_mut(); + let reply_string: *mut u8 = + RedisModule_CallReplyVerbatim.unwrap()(reply, &mut len, &mut format) as *mut u8; + let res = String::from_utf8(slice::from_raw_parts(reply_string, len).to_vec()).unwrap(); + let format = + String::from_utf8(slice::from_raw_parts(format as *mut u8, 3).to_vec()).unwrap(); + (format, res) + } +} + #[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn call_reply_array_element( reply: *mut RedisModuleCallReply, @@ -209,6 +250,25 @@ pub fn call_reply_array_element( unsafe { RedisModule_CallReplyArrayElement.unwrap()(reply, idx) } } +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn call_reply_map_element( + reply: *mut RedisModuleCallReply, + idx: usize, +) -> (*mut RedisModuleCallReply, *mut RedisModuleCallReply) { + let mut key: *mut RedisModuleCallReply = ptr::null_mut(); + let mut val: *mut RedisModuleCallReply = ptr::null_mut(); + unsafe { RedisModule_CallReplyMapElement.unwrap()(reply, idx, &mut key, &mut val) }; + (key, val) +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn call_reply_set_element( + reply: *mut RedisModuleCallReply, + idx: usize, +) -> *mut RedisModuleCallReply { + unsafe { RedisModule_CallReplySetElement.unwrap()(reply, idx) } +} + #[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn call_reply_length(reply: *mut RedisModuleCallReply) -> usize { unsafe { RedisModule_CallReplyLength.unwrap()(reply) } @@ -495,7 +555,31 @@ pub fn replicate(ctx: *mut RedisModuleCtx, command: &str, args: &[&str]) -> Stat let terminated_args: Vec = args.iter().map(|s| RedisString::create(ctx, s)).collect(); - let inner_args: Vec<*mut RedisModuleString> = terminated_args.iter().map(|s| s.inner).collect(); + replicate_redis_strings(ctx, command, &terminated_args) +} + +pub fn replicate_slices(ctx: *mut RedisModuleCtx, command: &str, args: &[&[u8]]) -> Status { + let terminated_args: Vec = args + .iter() + .map(|s| RedisString::create_from_slice(ctx, s)) + .collect(); + + replicate_redis_strings(ctx, command, &terminated_args) +} + +// The lint is disabled since all the behaviour is controlled via Redis, +// and all the pointers if dereferenced will be dereferenced by the module. +// +// Since we can't know the logic of Redis when it comes to pointers, we +// can't say whether passing a null pointer is okay to a redis function +// or not. So we can neither deny it is valid nor confirm. +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn replicate_redis_strings( + ctx: *mut RedisModuleCtx, + command: &str, + args: &[RedisString], +) -> Status { + let inner_args: Vec<*mut RedisModuleString> = args.iter().map(|s| s.inner).collect(); let cmd = CString::new(command).unwrap(); @@ -505,7 +589,7 @@ pub fn replicate(ctx: *mut RedisModuleCtx, command: &str, args: &[&str]) -> Stat cmd.as_ptr(), FMT, inner_args.as_ptr() as *mut c_char, - terminated_args.len(), + args.len(), ) .into() } @@ -526,6 +610,11 @@ pub fn save_string(rdb: *mut RedisModuleIO, buf: &str) { unsafe { RedisModule_SaveStringBuffer.unwrap()(rdb, buf.as_ptr().cast::(), buf.len()) }; } +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn save_slice(rdb: *mut RedisModuleIO, buf: &[u8]) { + unsafe { RedisModule_SaveStringBuffer.unwrap()(rdb, buf.as_ptr().cast::(), buf.len()) }; +} + #[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn save_double(rdb: *mut RedisModuleIO, val: f64) { unsafe { RedisModule_SaveDouble.unwrap()(rdb, val) }; @@ -639,7 +728,7 @@ pub fn get_keyspace_events() -> NotifyEvent { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Version { pub major: i32, pub minor: i32, diff --git a/src/redismodule.rs b/src/redismodule.rs index 1b87d494..a77d2cfd 100644 --- a/src/redismodule.rs +++ b/src/redismodule.rs @@ -108,6 +108,15 @@ impl RedisString { Self { ctx, inner } } + #[allow(clippy::not_unsafe_ptr_arg_deref)] + pub fn create_from_slice(ctx: *mut raw::RedisModuleCtx, s: &[u8]) -> Self { + let inner = unsafe { + raw::RedisModule_CreateString.unwrap()(ctx, s.as_ptr() as *const c_char, s.len()) + }; + + Self { ctx, inner } + } + pub fn from_redis_module_string( ctx: *mut raw::RedisModuleCtx, inner: *mut raw::RedisModuleString, @@ -148,7 +157,14 @@ impl RedisString { Self::string_as_slice(self.inner) } - fn string_as_slice<'a>(ptr: *const raw::RedisModuleString) -> &'a [u8] { + // The lint is disabled since all the behaviour is controlled via Redis, + // and all the pointers if dereferenced will be dereferenced by the module. + // + // Since we can't know the logic of Redis when it comes to pointers, we + // can't say whether passing a null pointer is okay to a redis function + // or not. So we can neither deny it is valid nor confirm. + #[allow(clippy::not_unsafe_ptr_arg_deref)] + pub fn string_as_slice<'a>(ptr: *const raw::RedisModuleString) -> &'a [u8] { let mut len: libc::size_t = 0; let bytes = unsafe { raw::RedisModule_StringPtrLen.unwrap()(ptr, &mut len) }; diff --git a/src/redisvalue.rs b/src/redisvalue.rs index 64bc089e..91ec0abc 100644 --- a/src/redisvalue.rs +++ b/src/redisvalue.rs @@ -1,4 +1,5 @@ use crate::RedisString; +use std::collections::{HashMap, HashSet}; #[derive(Debug, PartialEq)] pub enum RedisValue { @@ -11,6 +12,12 @@ pub enum RedisValue { Float(f64), Array(Vec), Null, + Map(HashMap, RedisValue>), + Set(HashSet>), + Bool(bool), + Double(f64), + BigNumber(String), + VerbatimString((String, String)), NoReply, // No reply at all (as opposed to a Null reply) } diff --git a/src/stream.rs b/src/stream.rs new file mode 100644 index 00000000..4e8d572c --- /dev/null +++ b/src/stream.rs @@ -0,0 +1,86 @@ +use crate::key::RedisKey; +use crate::raw; +use crate::RedisError; +use crate::RedisString; +use std::os::raw::c_long; +use std::ptr; + +pub struct StreamRecord { + pub id: raw::RedisModuleStreamID, + pub fields: Vec<(RedisString, RedisString)>, +} + +pub struct StreamIterator { + ctx: *mut raw::RedisModuleCtx, + key_inner: *mut raw::RedisModuleKey, +} + +impl StreamIterator { + pub(crate) fn new( + key: &RedisKey, + mut from: Option, + mut to: Option, + exclusive: bool, + ) -> Result { + let flags = if exclusive { + raw::REDISMODULE_STREAM_ITERATOR_EXCLUSIVE as i32 + } else { + 0 + }; + let res = unsafe { + raw::RedisModule_StreamIteratorStart.unwrap()( + key.key_inner, + flags, + from.as_mut().map_or(ptr::null_mut(), |v| v), + to.as_mut().map_or(ptr::null_mut(), |v| v), + ) + }; + if res != raw::REDISMODULE_OK as i32 { + Err(RedisError::Str("Failed creating stream iterator")) + } else { + Ok(StreamIterator { + ctx: key.ctx, + key_inner: key.key_inner, + }) + } + } +} + +impl Iterator for StreamIterator { + type Item = StreamRecord; + + fn next(&mut self) -> Option { + let mut id = raw::RedisModuleStreamID { ms: 0, seq: 0 }; + let mut num_fields: c_long = 0; + let mut field_name: *mut raw::RedisModuleString = ptr::null_mut(); + let mut field_val: *mut raw::RedisModuleString = ptr::null_mut(); + if unsafe { + raw::RedisModule_StreamIteratorNextID.unwrap()(self.key_inner, &mut id, &mut num_fields) + } != raw::REDISMODULE_OK as i32 + { + return None; + } + let mut fields = Vec::new(); + while unsafe { + raw::RedisModule_StreamIteratorNextField.unwrap()( + self.key_inner, + &mut field_name, + &mut field_val, + ) + } == raw::REDISMODULE_OK as i32 + { + fields.push(( + RedisString::from_redis_module_string(self.ctx, field_name), + RedisString::from_redis_module_string(self.ctx, field_val), + )); + } + + Some(StreamRecord { id, fields }) + } +} + +impl Drop for StreamIterator { + fn drop(&mut self) { + unsafe { raw::RedisModule_StreamIteratorDelete.unwrap()(self.key_inner) }; + } +} diff --git a/src/utils.rs b/src/utils.rs index b1ae7063..507688f2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,7 +3,7 @@ use regex::Regex; /// Extracts regexp captures /// /// Extract from `s` the captures defined in `reg_exp` -pub fn get_regexp_captures<'a, 'b>(s: &'a str, reg_exp: &'b str) -> Option> { +pub fn get_regexp_captures<'a>(s: &'a str, reg_exp: &str) -> Option> { Regex::new(reg_exp).map_or_else( |_| None, |re| { diff --git a/tests/integration.rs b/tests/integration.rs index e6ad5404..149207a6 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -51,8 +51,8 @@ fn test_keys_pos() -> Result<()> { Ok(()) } -#[test] -fn test_test_helper_version() -> Result<()> { +#[cfg_attr(feature = "experimental-api", test)] +fn test_helper_version() -> Result<()> { let port: u16 = 6481; let _guards = vec![start_redis_server_with_module("test_helper", port) .with_context(|| "failed to start redis server")?]; @@ -73,8 +73,7 @@ fn test_test_helper_version() -> Result<()> { Ok(()) } -#[cfg(feature = "experimental-api")] -#[test] +#[cfg_attr(feature = "experimental-api", test)] fn test_command_name() -> Result<()> { use redis_module::RedisValue; @@ -111,8 +110,8 @@ fn test_command_name() -> Result<()> { Ok(()) } -#[test] -fn test_test_helper_info() -> Result<()> { +#[cfg_attr(feature = "experimental-api", test)] +fn test_helper_info() -> Result<()> { let port: u16 = 6483; let _guards = vec![start_redis_server_with_module("test_helper", port) .with_context(|| "failed to start redis server")?]; @@ -130,7 +129,7 @@ fn test_test_helper_info() -> Result<()> { #[allow(unused_must_use)] #[test] -fn test_test_helper_err() -> Result<()> { +fn test_helper_err() -> Result<()> { let port: u16 = 6484; let _guards = vec![start_redis_server_with_module("hello", port) .with_context(|| "failed to start redis server")?]; @@ -148,3 +147,170 @@ fn test_test_helper_err() -> Result<()> { Ok(()) } + +#[cfg_attr(feature = "experimental-api", test)] +fn test_stream_reader() -> Result<()> { + let port: u16 = 6485; + let _guards = vec![start_redis_server_with_module("stream", port) + .with_context(|| "failed to start redis server")?]; + let mut con = + get_redis_connection(port).with_context(|| "failed to connect to redis server")?; + + let _: String = redis::cmd("XADD") + .arg(&["s", "1-1", "foo", "bar"]) + .query(&mut con) + .with_context(|| "failed to add data to the stream")?; + + let _: String = redis::cmd("XADD") + .arg(&["s", "1-2", "foo", "bar"]) + .query(&mut con) + .with_context(|| "failed to add data to the stream")?; + + let res: String = redis::cmd("STREAM_POP") + .arg(&["s"]) + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + assert_eq!(res, "1-1"); + + let res: String = redis::cmd("STREAM_POP") + .arg(&["s"]) + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + assert_eq!(res, "1-2"); + + let res: usize = redis::cmd("XLEN") + .arg(&["s"]) + .query(&mut con) + .with_context(|| "failed to add data to the stream")?; + + assert_eq!(res, 0); + + Ok(()) +} + +#[cfg_attr(feature = "experimental-api", test)] +fn test_flush_events() -> Result<()> { + let port: u16 = 6486; + let _guards = vec![start_redis_server_with_module("server_events", port) + .with_context(|| "failed to start redis server")?]; + let mut con = + get_redis_connection(port).with_context(|| "failed to connect to redis server")?; + + let _: String = redis::cmd("FLUSHALL") + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + + let res: usize = redis::cmd("NUM_FLUSHED") + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + assert_eq!(res, 2); // 2 is because we have flush start and end events + + let _: String = redis::cmd("FLUSHALL") + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + + let res: usize = redis::cmd("NUM_FLUSHED") + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + assert_eq!(res, 4); + + Ok(()) +} + +#[cfg_attr(feature = "experimental-api", test)] +fn test_role_changed_events() -> Result<()> { + let port: u16 = 6487; + let _guards = vec![start_redis_server_with_module("server_events", port) + .with_context(|| "failed to start redis server")?]; + let mut con = + get_redis_connection(port).with_context(|| "failed to connect to redis server")?; + + let _: String = redis::cmd("replicaof") + .arg(&["127.0.0.1", "33333"]) + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + + let res: usize = redis::cmd("NUM_ROLED_CHANGED") + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + assert_eq!(res, 1); + + let _: String = redis::cmd("replicaof") + .arg(&["no", "one"]) + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + + let res: usize = redis::cmd("NUM_ROLED_CHANGED") + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + assert_eq!(res, 2); + + Ok(()) +} + +#[cfg_attr(feature = "experimental-api", test)] +fn test_loading_events() -> Result<()> { + let port: u16 = 6488; + let _guards = vec![start_redis_server_with_module("server_events", port) + .with_context(|| "failed to start redis server")?]; + let mut con = + get_redis_connection(port).with_context(|| "failed to connect to redis server")?; + + // initial load + let initial_num_loading: usize = redis::cmd("NUM_LOADING") + .query(&mut con) + .with_context(|| "failed to run NUM_LOADING")?; + + let _: String = redis::cmd("debug") + .arg(&["reload"]) + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + + let res: usize = redis::cmd("NUM_LOADING") + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + assert_eq!(res, initial_num_loading + 2); // 2 is because we have loading start and end events + + let _: String = redis::cmd("debug") + .arg(&["reload"]) + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + + let res: usize = redis::cmd("NUM_LOADING") + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + assert_eq!(res, initial_num_loading + 4); + + Ok(()) +} + +#[cfg_attr(feature = "experimental-api", test)] +fn test_key_scan() -> Result<()> { + let port: u16 = 6489; + let _guards = vec![start_redis_server_with_module("scan", port) + .with_context(|| "failed to start redis server")?]; + let mut con = + get_redis_connection(port).with_context(|| "failed to connect to redis server")?; + + let _: String = redis::cmd("set") + .arg(&["x", "1"]) + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + + let res: Vec = redis::cmd("SCAN_KEYS") + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + assert_eq!(res.len(), 1); + + let _: String = redis::cmd("set") + .arg(&["y", "1"]) + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + + let res: Vec = redis::cmd("SCAN_KEYS") + .query(&mut con) + .with_context(|| "failed to run keys_pos")?; + assert_eq!(res.len(), 2); + + Ok(()) +}