valkey_module/
raw.rs

1// Allow dead code in here in case I want to publish it as a crate at some
2// point.
3#![allow(dead_code)]
4
5extern crate enum_primitive_derive;
6extern crate libc;
7extern crate num_traits;
8
9use std::cmp::Ordering;
10use std::ffi::{c_ulonglong, CStr, CString};
11use std::os::raw::{c_char, c_double, c_int, c_long, c_longlong, c_void};
12use std::ptr;
13use std::slice;
14
15use crate::ValkeyResult;
16use bitflags::bitflags;
17use enum_primitive_derive::Primitive;
18use libc::size_t;
19use num_traits::FromPrimitive;
20
21use crate::error::Error;
22pub use crate::redisraw::bindings::*;
23use crate::{context::StrCallArgs, Context, ValkeyString};
24use crate::{RedisBuffer, ValkeyError};
25
26const GENERIC_ERROR_MESSAGE: &str = "Generic error.";
27
28bitflags! {
29    pub struct KeyMode: c_int {
30        const READ = REDISMODULE_READ as c_int;
31        const WRITE = REDISMODULE_WRITE as c_int;
32    }
33}
34
35bitflags! {
36    pub struct ModuleOptions: c_int {
37        const HANDLE_IO_ERRORS = REDISMODULE_OPTIONS_HANDLE_IO_ERRORS as c_int;
38        const NO_IMPLICIT_SIGNAL_MODIFIED = REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED as c_int;
39        const HANDLE_REPL_ASYNC_LOAD = REDISMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD as c_int;
40        const ALLOW_NESTED_KEYSPACE_NOTIFICATIONS = REDISMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS as c_int;
41    }
42}
43
44#[derive(Primitive, Debug, PartialEq, Eq)]
45pub enum KeyType {
46    Empty = REDISMODULE_KEYTYPE_EMPTY,
47    String = REDISMODULE_KEYTYPE_STRING,
48    List = REDISMODULE_KEYTYPE_LIST,
49    Hash = REDISMODULE_KEYTYPE_HASH,
50    Set = REDISMODULE_KEYTYPE_SET,
51    ZSet = REDISMODULE_KEYTYPE_ZSET,
52    Module = REDISMODULE_KEYTYPE_MODULE,
53    Stream = REDISMODULE_KEYTYPE_STREAM,
54}
55
56impl From<c_int> for KeyType {
57    fn from(v: c_int) -> Self {
58        Self::from_i32(v).unwrap()
59    }
60}
61
62#[derive(Primitive, Debug, PartialEq, Eq)]
63pub enum Where {
64    ListHead = REDISMODULE_LIST_HEAD,
65    ListTail = REDISMODULE_LIST_TAIL,
66}
67
68#[derive(Primitive, Debug, PartialEq, Eq)]
69pub enum ReplyType {
70    Unknown = REDISMODULE_REPLY_UNKNOWN,
71    String = REDISMODULE_REPLY_STRING,
72    Error = REDISMODULE_REPLY_ERROR,
73    Integer = REDISMODULE_REPLY_INTEGER,
74    Array = REDISMODULE_REPLY_ARRAY,
75    Null = REDISMODULE_REPLY_NULL,
76    Map = REDISMODULE_REPLY_MAP,
77    Set = REDISMODULE_REPLY_SET,
78    Bool = REDISMODULE_REPLY_BOOL,
79    Double = REDISMODULE_REPLY_DOUBLE,
80    BigNumber = REDISMODULE_REPLY_BIG_NUMBER,
81    VerbatimString = REDISMODULE_REPLY_VERBATIM_STRING,
82}
83
84impl From<c_int> for ReplyType {
85    fn from(v: c_int) -> Self {
86        Self::from_i32(v).unwrap()
87    }
88}
89
90#[derive(Primitive, Debug, PartialEq, Eq)]
91pub enum Aux {
92    Before = REDISMODULE_AUX_BEFORE_RDB,
93    After = REDISMODULE_AUX_AFTER_RDB,
94}
95
96#[derive(Primitive, Debug, PartialEq, Eq)]
97pub enum Status {
98    Ok = REDISMODULE_OK,
99    Err = REDISMODULE_ERR,
100}
101
102impl From<Status> for ValkeyResult<()> {
103    fn from(value: Status) -> Self {
104        match value {
105            Status::Ok => Ok(()),
106            Status::Err => Err(ValkeyError::Str(GENERIC_ERROR_MESSAGE)),
107        }
108    }
109}
110
111impl From<c_int> for Status {
112    fn from(v: c_int) -> Self {
113        Self::from_i32(v).unwrap()
114    }
115}
116
117impl From<Status> for Result<(), &str> {
118    fn from(s: Status) -> Self {
119        match s {
120            Status::Ok => Ok(()),
121            Status::Err => Err(GENERIC_ERROR_MESSAGE),
122        }
123    }
124}
125
126bitflags! {
127    #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
128    pub struct NotifyEvent : c_int {
129        const GENERIC = REDISMODULE_NOTIFY_GENERIC;
130        const STRING = REDISMODULE_NOTIFY_STRING;
131        const LIST = REDISMODULE_NOTIFY_LIST;
132        const SET = REDISMODULE_NOTIFY_SET;
133        const HASH = REDISMODULE_NOTIFY_HASH;
134        const ZSET = REDISMODULE_NOTIFY_ZSET;
135        const EXPIRED = REDISMODULE_NOTIFY_EXPIRED;
136        const EVICTED = REDISMODULE_NOTIFY_EVICTED;
137        const STREAM = REDISMODULE_NOTIFY_STREAM;
138        /// Available only starting from Redis `7.0.1`.
139        const NEW = REDISMODULE_NOTIFY_NEW;
140        const MODULE = REDISMODULE_NOTIFY_MODULE;
141        const LOADED = REDISMODULE_NOTIFY_LOADED;
142        const MISSED = REDISMODULE_NOTIFY_KEY_MISS;
143        /// Does not include the [`Self::MISSED`] and [`Self::NEW`].
144        ///
145        /// Includes [`Self::GENERIC`], [`Self::STRING`], [`Self::LIST`],
146        /// [`Self::SET`], [`Self::HASH`], [`Self::ZSET`], [`Self::EXPIRED`],
147        /// [`Self::EVICTED`], [`Self::STREAM`], [`Self::MODULE`].
148        const ALL = REDISMODULE_NOTIFY_ALL;
149        const TRIMMED = REDISMODULE_NOTIFY_TRIMMED;
150    }
151}
152
153#[derive(Debug)]
154pub enum CommandFlag {
155    Write,
156    Readonly,
157    Denyoom,
158    Admin,
159    Pubsub,
160    Noscript,
161    Random,
162    SortForScript,
163    Loading,
164    Stale,
165    SkipMonitor,
166    Asking,
167    Fast,
168    Movablekeys,
169}
170
171const fn command_flag_repr(flag: &CommandFlag) -> &'static str {
172    use crate::raw::CommandFlag::*;
173    match flag {
174        Write => "write",
175        Readonly => "readonly",
176        Denyoom => "denyoom",
177        Admin => "admin",
178        Pubsub => "pubsub",
179        Noscript => "noscript",
180        Random => "random",
181        SortForScript => "sort_for_script",
182        Loading => "loading",
183        Stale => "stale",
184        SkipMonitor => "skip_monitor",
185        Asking => "asking",
186        Fast => "fast",
187        Movablekeys => "movablekeys",
188    }
189}
190
191// This is the one static function we need to initialize a module.
192// bindgen does not generate it for us (probably since it's defined as static in redismodule.h).
193#[allow(improper_ctypes)]
194#[link(name = "redismodule", kind = "static")]
195extern "C" {
196    pub fn Export_RedisModule_Init(
197        ctx: *mut RedisModuleCtx,
198        module_name: *const c_char,
199        module_version: c_int,
200        api_version: c_int,
201    ) -> c_int;
202
203    pub fn Export_RedisModule_InitAPI(ctx: *mut RedisModuleCtx) -> c_void;
204}
205
206// This is the one static function we need to initialize a module.
207// bindgen does not generate it for us (probably since it's defined as static in valkeymodule.h).
208#[allow(improper_ctypes)]
209#[link(name = "valkeymodule", kind = "static")]
210extern "C" {
211    pub fn Export_ValkeyModule_Init(
212        ctx: *mut ValkeyModuleCtx,
213        module_name: *const c_char,
214        module_version: c_int,
215        api_version: c_int,
216    ) -> c_int;
217
218    pub fn Export_ValkeyModule_InitAPI(ctx: *mut ValkeyModuleCtx) -> c_void;
219}
220
221///////////////////////////////////////////////////////////////
222
223pub const FMT: *const c_char = b"v\0".as_ptr().cast::<c_char>();
224
225// REDISMODULE_HASH_DELETE is defined explicitly here because bindgen cannot
226// parse typecasts in C macro constants yet.
227// See https://github.com/rust-lang/rust-bindgen/issues/316
228pub const REDISMODULE_HASH_DELETE: *const RedisModuleString = 1 as *const RedisModuleString;
229
230// Helper functions for the raw bindings.
231
232#[allow(clippy::not_unsafe_ptr_arg_deref)]
233pub fn call_reply_type(reply: *mut RedisModuleCallReply) -> ReplyType {
234    unsafe {
235        // TODO: Cache the unwrapped functions and use them instead of unwrapping every time?
236        RedisModule_CallReplyType.unwrap()(reply).into()
237    }
238}
239
240#[allow(clippy::not_unsafe_ptr_arg_deref)]
241pub fn free_call_reply(reply: *mut RedisModuleCallReply) {
242    unsafe { RedisModule_FreeCallReply.unwrap()(reply) }
243}
244
245#[allow(clippy::not_unsafe_ptr_arg_deref)]
246pub fn call_reply_integer(reply: *mut RedisModuleCallReply) -> c_longlong {
247    unsafe { RedisModule_CallReplyInteger.unwrap()(reply) }
248}
249
250/// # Panics
251///
252/// Panics if the Valkey server doesn't support replying with bool (since RESP3).
253#[allow(clippy::not_unsafe_ptr_arg_deref)]
254pub fn call_reply_bool(reply: *mut RedisModuleCallReply) -> bool {
255    (unsafe { RedisModule_CallReplyBool.unwrap()(reply) } != 0)
256}
257
258/// # Panics
259///
260/// Panics if the Valkey server doesn't support replying with bool (since RESP3).
261#[allow(clippy::not_unsafe_ptr_arg_deref)]
262pub fn call_reply_double(reply: *mut RedisModuleCallReply) -> f64 {
263    unsafe { RedisModule_CallReplyDouble.unwrap()(reply) }
264}
265
266/// # Panics
267///
268/// Panics if the Valkey server doesn't support replying with bool (since RESP3).
269#[allow(clippy::not_unsafe_ptr_arg_deref)]
270pub fn call_reply_big_number(reply: *mut RedisModuleCallReply) -> Option<String> {
271    unsafe {
272        let mut len: size_t = 0;
273        let reply_string: *mut u8 =
274            RedisModule_CallReplyBigNumber.unwrap()(reply, &mut len) as *mut u8;
275        if reply_string.is_null() {
276            return None;
277        }
278        String::from_utf8(slice::from_raw_parts(reply_string, len).to_vec()).ok()
279    }
280}
281
282/// # Panics
283///
284/// Panics if the Valkey server doesn't support replying with bool (since RESP3).
285#[allow(clippy::not_unsafe_ptr_arg_deref)]
286pub fn call_reply_verbatim_string(reply: *mut RedisModuleCallReply) -> Option<(String, Vec<u8>)> {
287    unsafe {
288        let mut len: size_t = 0;
289        let format: *const u8 = ptr::null();
290        let reply_string: *mut u8 =
291            RedisModule_CallReplyVerbatim.unwrap()(reply, &mut len, &mut (format as *const c_char))
292                as *mut u8;
293        if reply_string.is_null() {
294            return None;
295        }
296        Some((
297            String::from_utf8(slice::from_raw_parts(format, 3).to_vec()).ok()?,
298            slice::from_raw_parts(reply_string, len).to_vec(),
299        ))
300    }
301}
302
303#[allow(clippy::not_unsafe_ptr_arg_deref)]
304pub fn call_reply_array_element(
305    reply: *mut RedisModuleCallReply,
306    idx: usize,
307) -> *mut RedisModuleCallReply {
308    unsafe { RedisModule_CallReplyArrayElement.unwrap()(reply, idx) }
309}
310
311/// # Panics
312///
313/// Panics if the Valkey server doesn't support replying with bool (since RESP3).
314#[allow(clippy::not_unsafe_ptr_arg_deref)]
315pub fn call_reply_set_element(
316    reply: *mut RedisModuleCallReply,
317    idx: usize,
318) -> *mut RedisModuleCallReply {
319    unsafe { RedisModule_CallReplySetElement.unwrap()(reply, idx) }
320}
321
322/// # Panics
323///
324/// Panics if the Valkey server doesn't support replying with bool (since RESP3).
325#[allow(clippy::not_unsafe_ptr_arg_deref)]
326pub fn call_reply_map_element(
327    reply: *mut RedisModuleCallReply,
328    idx: usize,
329) -> (*mut RedisModuleCallReply, *mut RedisModuleCallReply) {
330    let mut key: *mut RedisModuleCallReply = ptr::null_mut();
331    let mut val: *mut RedisModuleCallReply = ptr::null_mut();
332    unsafe { RedisModule_CallReplyMapElement.unwrap()(reply, idx, &mut key, &mut val) };
333    (key, val)
334}
335
336#[allow(clippy::not_unsafe_ptr_arg_deref)]
337pub fn call_reply_length(reply: *mut RedisModuleCallReply) -> usize {
338    unsafe { RedisModule_CallReplyLength.unwrap()(reply) }
339}
340
341#[allow(clippy::not_unsafe_ptr_arg_deref)]
342pub fn call_reply_string_ptr(reply: *mut RedisModuleCallReply, len: *mut size_t) -> *const c_char {
343    unsafe { RedisModule_CallReplyStringPtr.unwrap()(reply, len) }
344}
345
346#[allow(clippy::not_unsafe_ptr_arg_deref)]
347pub fn call_reply_string(reply: *mut RedisModuleCallReply) -> Option<String> {
348    unsafe {
349        let mut len: size_t = 0;
350        let reply_string: *mut u8 =
351            RedisModule_CallReplyStringPtr.unwrap()(reply, &mut len) as *mut u8;
352        if reply_string.is_null() {
353            return None;
354        }
355        String::from_utf8(slice::from_raw_parts(reply_string, len).to_vec()).ok()
356    }
357}
358
359#[allow(clippy::not_unsafe_ptr_arg_deref)]
360#[inline]
361pub fn close_key(kp: *mut RedisModuleKey) {
362    unsafe { RedisModule_CloseKey.unwrap()(kp) }
363}
364
365#[allow(clippy::not_unsafe_ptr_arg_deref)]
366#[inline]
367pub fn open_key(
368    ctx: *mut RedisModuleCtx,
369    keyname: *mut RedisModuleString,
370    mode: KeyMode,
371) -> *mut RedisModuleKey {
372    unsafe { RedisModule_OpenKey.unwrap()(ctx, keyname, mode.bits()).cast::<RedisModuleKey>() }
373}
374
375#[allow(clippy::not_unsafe_ptr_arg_deref)]
376#[inline]
377pub(crate) fn open_key_with_flags(
378    ctx: *mut RedisModuleCtx,
379    keyname: *mut RedisModuleString,
380    mode: KeyMode,
381    flags: c_int,
382) -> *mut RedisModuleKey {
383    unsafe {
384        RedisModule_OpenKey.unwrap()(ctx, keyname, mode.bits() | flags).cast::<RedisModuleKey>()
385    }
386}
387
388#[allow(clippy::not_unsafe_ptr_arg_deref)]
389#[inline]
390pub fn reply_with_array(ctx: *mut RedisModuleCtx, len: c_long) -> Status {
391    unsafe { RedisModule_ReplyWithArray.unwrap()(ctx, len).into() }
392}
393
394#[allow(clippy::not_unsafe_ptr_arg_deref)]
395#[inline]
396pub fn reply_with_map(ctx: *mut RedisModuleCtx, len: c_long) -> Status {
397    unsafe {
398        RedisModule_ReplyWithMap
399            .map_or_else(
400                || RedisModule_ReplyWithArray.unwrap()(ctx, len * 2),
401                |f| f(ctx, len),
402            )
403            .into()
404    }
405}
406
407#[allow(clippy::not_unsafe_ptr_arg_deref)]
408#[inline]
409pub fn reply_with_set(ctx: *mut RedisModuleCtx, len: c_long) -> Status {
410    unsafe {
411        RedisModule_ReplyWithSet
412            .map_or_else(
413                || RedisModule_ReplyWithArray.unwrap()(ctx, len * 2),
414                |f| f(ctx, len),
415            )
416            .into()
417    }
418}
419
420#[allow(clippy::not_unsafe_ptr_arg_deref)]
421#[inline]
422pub fn reply_with_attribute(ctx: *mut RedisModuleCtx, len: c_long) -> Status {
423    unsafe { RedisModule_ReplyWithAttribute.unwrap()(ctx, len).into() }
424}
425
426#[allow(clippy::not_unsafe_ptr_arg_deref)]
427pub fn reply_with_error(ctx: *mut RedisModuleCtx, err: *const c_char) {
428    unsafe {
429        let msg = Context::str_as_legal_resp_string(CStr::from_ptr(err).to_str().unwrap());
430        RedisModule_ReplyWithError.unwrap()(ctx, msg.as_ptr());
431    }
432}
433
434#[allow(clippy::not_unsafe_ptr_arg_deref)]
435#[inline]
436pub fn reply_with_null(ctx: *mut RedisModuleCtx) -> Status {
437    unsafe { RedisModule_ReplyWithNull.unwrap()(ctx).into() }
438}
439
440#[allow(clippy::not_unsafe_ptr_arg_deref)]
441#[inline]
442pub fn reply_with_bool(ctx: *mut RedisModuleCtx, b: c_int) -> Status {
443    unsafe { RedisModule_ReplyWithBool.unwrap()(ctx, b).into() }
444}
445
446#[allow(clippy::not_unsafe_ptr_arg_deref)]
447#[inline]
448pub fn reply_with_long_long(ctx: *mut RedisModuleCtx, ll: c_longlong) -> Status {
449    unsafe { RedisModule_ReplyWithLongLong.unwrap()(ctx, ll).into() }
450}
451
452#[allow(clippy::not_unsafe_ptr_arg_deref)]
453#[inline]
454pub fn reply_with_double(ctx: *mut RedisModuleCtx, f: c_double) -> Status {
455    unsafe { RedisModule_ReplyWithDouble.unwrap()(ctx, f).into() }
456}
457
458#[allow(clippy::not_unsafe_ptr_arg_deref)]
459#[inline]
460pub fn reply_with_string(ctx: *mut RedisModuleCtx, s: *mut RedisModuleString) -> Status {
461    unsafe { RedisModule_ReplyWithString.unwrap()(ctx, s).into() }
462}
463
464#[allow(clippy::not_unsafe_ptr_arg_deref)]
465#[inline]
466pub fn reply_with_simple_string(ctx: *mut RedisModuleCtx, s: *const c_char) -> Status {
467    unsafe { RedisModule_ReplyWithSimpleString.unwrap()(ctx, s).into() }
468}
469
470#[allow(clippy::not_unsafe_ptr_arg_deref)]
471#[inline]
472pub fn reply_with_string_buffer(ctx: *mut RedisModuleCtx, s: *const c_char, len: size_t) -> Status {
473    unsafe { RedisModule_ReplyWithStringBuffer.unwrap()(ctx, s, len).into() }
474}
475
476#[allow(clippy::not_unsafe_ptr_arg_deref)]
477#[inline]
478pub fn reply_with_big_number(ctx: *mut RedisModuleCtx, s: *const c_char, len: size_t) -> Status {
479    unsafe { RedisModule_ReplyWithBigNumber.unwrap()(ctx, s, len).into() }
480}
481
482#[allow(clippy::not_unsafe_ptr_arg_deref)]
483#[inline]
484pub fn reply_with_verbatim_string(
485    ctx: *mut RedisModuleCtx,
486    s: *const c_char,
487    len: size_t,
488    format: *const c_char,
489) -> Status {
490    unsafe { RedisModule_ReplyWithVerbatimStringType.unwrap()(ctx, s, len, format).into() }
491}
492
493// Sets the expiry on a key.
494//
495// Expire is in milliseconds.
496#[allow(clippy::not_unsafe_ptr_arg_deref)]
497#[inline]
498pub fn set_expire(key: *mut RedisModuleKey, expire: c_longlong) -> Status {
499    unsafe { RedisModule_SetExpire.unwrap()(key, expire).into() }
500}
501
502#[allow(clippy::not_unsafe_ptr_arg_deref)]
503#[inline]
504pub fn string_dma(key: *mut RedisModuleKey, len: *mut size_t, mode: KeyMode) -> *mut c_char {
505    unsafe { RedisModule_StringDMA.unwrap()(key, len, mode.bits()) }
506}
507
508#[allow(clippy::not_unsafe_ptr_arg_deref)]
509#[inline]
510pub fn string_truncate(key: *mut RedisModuleKey, new_len: size_t) -> Status {
511    unsafe { RedisModule_StringTruncate.unwrap()(key, new_len).into() }
512}
513
514#[allow(clippy::not_unsafe_ptr_arg_deref)]
515pub fn hash_get_multi<T>(
516    key: *mut RedisModuleKey,
517    fields: &[T],
518    values: &mut [*mut RedisModuleString],
519) -> Result<(), ValkeyError>
520where
521    T: Into<Vec<u8>> + Clone,
522{
523    assert_eq!(fields.len(), values.len());
524
525    let fields = fields
526        .iter()
527        .map(|e| CString::new(e.clone()))
528        .collect::<Result<Vec<CString>, _>>()?;
529
530    let mut fi = fields.iter();
531    let mut vi = values.iter_mut();
532
533    macro_rules! rm {
534        () => { unsafe {
535            RedisModule_HashGet.unwrap()(key, REDISMODULE_HASH_CFIELDS as i32,
536                                         ptr::null::<c_char>())
537        }};
538        ($($args:expr)*) => { unsafe {
539            RedisModule_HashGet.unwrap()(
540                key, REDISMODULE_HASH_CFIELDS as i32,
541                $($args),*,
542                ptr::null::<c_char>()
543            )
544        }};
545    }
546    macro_rules! f {
547        () => {
548            fi.next().unwrap().as_ptr()
549        };
550    }
551    macro_rules! v {
552        () => {
553            vi.next().unwrap()
554        };
555    }
556
557    // This convoluted code is necessary since Redis only exposes a varargs API for HashGet
558    // to modules. Unfortunately there's no straightforward or portable way of calling a
559    // a varargs function with a variable number of arguments that is determined at runtime.
560    // See also the following Redis ticket: https://github.com/redis/redis/issues/7860
561    let res = Status::from(match fields.len() {
562        0 => rm! {},
563        1 => rm! {f!() v!()},
564        2 => rm! {f!() v!() f!() v!()},
565        3 => rm! {f!() v!() f!() v!() f!() v!()},
566        4 => rm! {f!() v!() f!() v!() f!() v!() f!() v!()},
567        5 => rm! {f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()},
568        6 => rm! {f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()},
569        7 => rm! {
570            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
571            f!() v!()
572        },
573        8 => rm! {
574            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
575            f!() v!() f!() v!()
576        },
577        9 => rm! {
578            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
579            f!() v!() f!() v!() f!() v!()
580        },
581        10 => rm! {
582            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
583            f!() v!() f!() v!() f!() v!() f!() v!()
584        },
585        11 => rm! {
586            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
587            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
588        },
589        12 => rm! {
590            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
591            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
592        },
593        _ => panic!("Unsupported length"),
594    });
595
596    match res {
597        Status::Ok => Ok(()),
598        Status::Err => Err(ValkeyError::Str("ERR key is not a hash value")),
599    }
600}
601
602#[allow(clippy::not_unsafe_ptr_arg_deref)]
603#[inline]
604pub fn hash_set(key: *mut RedisModuleKey, field: &str, value: *mut RedisModuleString) -> Status {
605    let field = CString::new(field).unwrap();
606
607    unsafe {
608        RedisModule_HashSet.unwrap()(
609            key,
610            REDISMODULE_HASH_CFIELDS as i32,
611            field.as_ptr(),
612            value,
613            ptr::null::<c_char>(),
614        )
615        .into()
616    }
617}
618
619#[allow(clippy::not_unsafe_ptr_arg_deref)]
620#[inline]
621pub fn hash_del(key: *mut RedisModuleKey, field: &str) -> Status {
622    let field = CString::new(field).unwrap();
623
624    // TODO: Add hash_del_multi()
625    // Support to pass multiple fields is desired but is complicated.
626    // See hash_get_multi() and https://github.com/redis/redis/issues/7860
627
628    unsafe {
629        RedisModule_HashSet.unwrap()(
630            key,
631            REDISMODULE_HASH_CFIELDS as i32,
632            field.as_ptr(),
633            REDISMODULE_HASH_DELETE,
634            ptr::null::<c_char>(),
635        )
636        .into()
637    }
638}
639
640#[allow(clippy::not_unsafe_ptr_arg_deref)]
641#[inline]
642pub fn list_push(
643    key: *mut RedisModuleKey,
644    list_where: Where,
645    element: *mut RedisModuleString,
646) -> Status {
647    unsafe { RedisModule_ListPush.unwrap()(key, list_where as i32, element).into() }
648}
649
650#[allow(clippy::not_unsafe_ptr_arg_deref)]
651#[inline]
652pub fn list_pop(key: *mut RedisModuleKey, list_where: Where) -> *mut RedisModuleString {
653    unsafe { RedisModule_ListPop.unwrap()(key, list_where as i32) }
654}
655
656// Returns pointer to the C string, and sets len to its length
657#[allow(clippy::not_unsafe_ptr_arg_deref)]
658#[inline]
659pub fn string_ptr_len(s: *const RedisModuleString, len: *mut size_t) -> *const c_char {
660    unsafe { RedisModule_StringPtrLen.unwrap()(s, len) }
661}
662
663#[allow(clippy::not_unsafe_ptr_arg_deref)]
664#[inline]
665pub fn string_retain_string(ctx: *mut RedisModuleCtx, s: *mut RedisModuleString) {
666    unsafe { RedisModule_RetainString.unwrap()(ctx, s) }
667}
668
669#[allow(clippy::not_unsafe_ptr_arg_deref)]
670#[inline]
671pub fn string_to_longlong(s: *const RedisModuleString, len: *mut i64) -> Status {
672    unsafe { RedisModule_StringToLongLong.unwrap()(s, len).into() }
673}
674
675#[allow(clippy::not_unsafe_ptr_arg_deref)]
676#[inline]
677pub fn string_to_double(s: *const RedisModuleString, len: *mut f64) -> Status {
678    unsafe { RedisModule_StringToDouble.unwrap()(s, len).into() }
679}
680
681#[allow(clippy::not_unsafe_ptr_arg_deref)]
682#[inline]
683pub fn string_set(key: *mut RedisModuleKey, s: *mut RedisModuleString) -> Status {
684    unsafe { RedisModule_StringSet.unwrap()(key, s).into() }
685}
686
687#[allow(clippy::not_unsafe_ptr_arg_deref)]
688#[inline]
689pub fn replicate_verbatim(ctx: *mut RedisModuleCtx) -> Status {
690    unsafe { RedisModule_ReplicateVerbatim.unwrap()(ctx).into() }
691}
692
693fn load<F, T>(rdb: *mut RedisModuleIO, f: F) -> Result<T, Error>
694where
695    F: FnOnce(*mut RedisModuleIO) -> T,
696{
697    let res = f(rdb);
698    if is_io_error(rdb) {
699        Err(ValkeyError::short_read().into())
700    } else {
701        Ok(res)
702    }
703}
704
705#[allow(clippy::not_unsafe_ptr_arg_deref)]
706pub fn load_unsigned(rdb: *mut RedisModuleIO) -> Result<u64, Error> {
707    unsafe { load(rdb, |rdb| RedisModule_LoadUnsigned.unwrap()(rdb)) }
708}
709
710#[allow(clippy::not_unsafe_ptr_arg_deref)]
711pub fn load_signed(rdb: *mut RedisModuleIO) -> Result<i64, Error> {
712    unsafe { load(rdb, |rdb| RedisModule_LoadSigned.unwrap()(rdb)) }
713}
714
715#[allow(clippy::not_unsafe_ptr_arg_deref)]
716pub fn load_string(rdb: *mut RedisModuleIO) -> Result<ValkeyString, Error> {
717    let p = unsafe { load(rdb, |rdb| RedisModule_LoadString.unwrap()(rdb))? };
718    Ok(ValkeyString::from_redis_module_string(ptr::null_mut(), p))
719}
720
721#[allow(clippy::not_unsafe_ptr_arg_deref)]
722pub fn load_string_buffer(rdb: *mut RedisModuleIO) -> Result<RedisBuffer, Error> {
723    unsafe {
724        let mut len = 0;
725        let buffer = load(rdb, |rdb| {
726            RedisModule_LoadStringBuffer.unwrap()(rdb, &mut len)
727        })?;
728        Ok(RedisBuffer::new(buffer, len))
729    }
730}
731
732#[allow(clippy::not_unsafe_ptr_arg_deref)]
733pub fn replicate<'a, T: Into<StrCallArgs<'a>>>(
734    ctx: *mut RedisModuleCtx,
735    command: &str,
736    args: T,
737) -> Status {
738    let mut call_args: StrCallArgs = args.into();
739    let final_args = call_args.args_mut();
740
741    let cmd = CString::new(command).unwrap();
742
743    unsafe {
744        RedisModule_Replicate.unwrap()(
745            ctx,
746            cmd.as_ptr(),
747            FMT,
748            final_args.as_ptr(),
749            final_args.len(),
750        )
751        .into()
752    }
753}
754
755#[allow(clippy::not_unsafe_ptr_arg_deref)]
756pub fn load_double(rdb: *mut RedisModuleIO) -> Result<f64, Error> {
757    unsafe { load(rdb, |rdb| RedisModule_LoadDouble.unwrap()(rdb)) }
758}
759
760#[allow(clippy::not_unsafe_ptr_arg_deref)]
761pub fn load_float(rdb: *mut RedisModuleIO) -> Result<f32, Error> {
762    unsafe { load(rdb, |rdb| RedisModule_LoadFloat.unwrap()(rdb)) }
763}
764
765#[allow(clippy::not_unsafe_ptr_arg_deref)]
766pub fn save_string(rdb: *mut RedisModuleIO, buf: &str) {
767    unsafe { RedisModule_SaveStringBuffer.unwrap()(rdb, buf.as_ptr().cast::<c_char>(), buf.len()) };
768}
769
770#[allow(clippy::not_unsafe_ptr_arg_deref)]
771/// Save the `RedisString` into the RDB
772pub fn save_redis_string(rdb: *mut RedisModuleIO, s: &ValkeyString) {
773    unsafe { RedisModule_SaveString.unwrap()(rdb, s.inner) };
774}
775
776#[allow(clippy::not_unsafe_ptr_arg_deref)]
777/// Save the `&[u8]` into the RDB
778pub fn save_slice(rdb: *mut RedisModuleIO, buf: &[u8]) {
779    unsafe { RedisModule_SaveStringBuffer.unwrap()(rdb, buf.as_ptr().cast::<c_char>(), buf.len()) };
780}
781
782#[allow(clippy::not_unsafe_ptr_arg_deref)]
783pub fn save_double(rdb: *mut RedisModuleIO, val: f64) {
784    unsafe { RedisModule_SaveDouble.unwrap()(rdb, val) };
785}
786
787#[allow(clippy::not_unsafe_ptr_arg_deref)]
788pub fn save_signed(rdb: *mut RedisModuleIO, val: i64) {
789    unsafe { RedisModule_SaveSigned.unwrap()(rdb, val) };
790}
791
792#[allow(clippy::not_unsafe_ptr_arg_deref)]
793pub fn save_float(rdb: *mut RedisModuleIO, val: f32) {
794    unsafe { RedisModule_SaveFloat.unwrap()(rdb, val) };
795}
796
797#[allow(clippy::not_unsafe_ptr_arg_deref)]
798pub fn save_unsigned(rdb: *mut RedisModuleIO, val: u64) {
799    unsafe { RedisModule_SaveUnsigned.unwrap()(rdb, val) };
800}
801
802#[allow(clippy::not_unsafe_ptr_arg_deref)]
803pub fn string_compare(a: *mut RedisModuleString, b: *mut RedisModuleString) -> Ordering {
804    unsafe { RedisModule_StringCompare.unwrap()(a, b).cmp(&0) }
805}
806
807#[allow(clippy::not_unsafe_ptr_arg_deref)]
808pub fn string_append_buffer(
809    ctx: *mut RedisModuleCtx,
810    s: *mut RedisModuleString,
811    buff: &str,
812) -> Status {
813    unsafe {
814        RedisModule_StringAppendBuffer.unwrap()(ctx, s, buff.as_ptr().cast::<c_char>(), buff.len())
815            .into()
816    }
817}
818
819#[allow(clippy::not_unsafe_ptr_arg_deref)]
820pub fn subscribe_to_server_event(
821    ctx: *mut RedisModuleCtx,
822    event: RedisModuleEvent,
823    callback: RedisModuleEventCallback,
824) -> Status {
825    unsafe { RedisModule_SubscribeToServerEvent.unwrap()(ctx, event, callback).into() }
826}
827
828#[allow(clippy::not_unsafe_ptr_arg_deref)]
829pub fn register_info_function(ctx: *mut RedisModuleCtx, callback: RedisModuleInfoFunc) -> Status {
830    unsafe { RedisModule_RegisterInfoFunc.unwrap()(ctx, callback).into() }
831}
832
833#[allow(clippy::not_unsafe_ptr_arg_deref)]
834pub fn add_info_section(ctx: *mut RedisModuleInfoCtx, name: Option<&str>) -> Status {
835    name.map(|n| CString::new(n).unwrap()).map_or_else(
836        || unsafe { RedisModule_InfoAddSection.unwrap()(ctx, ptr::null_mut()).into() },
837        |n| unsafe { RedisModule_InfoAddSection.unwrap()(ctx, n.as_ptr()).into() },
838    )
839}
840
841#[allow(clippy::not_unsafe_ptr_arg_deref)]
842pub fn add_info_field_str(ctx: *mut RedisModuleInfoCtx, name: &str, content: &str) -> Status {
843    let name = CString::new(name).unwrap();
844    let content = ValkeyString::create(None, content);
845    unsafe { RedisModule_InfoAddFieldString.unwrap()(ctx, name.as_ptr(), content.inner).into() }
846}
847
848#[allow(clippy::not_unsafe_ptr_arg_deref)]
849pub fn add_info_field_long_long(
850    ctx: *mut RedisModuleInfoCtx,
851    name: &str,
852    value: c_longlong,
853) -> Status {
854    let name = CString::new(name).unwrap();
855    unsafe { RedisModule_InfoAddFieldLongLong.unwrap()(ctx, name.as_ptr(), value).into() }
856}
857
858#[allow(clippy::not_unsafe_ptr_arg_deref)]
859pub fn add_info_field_unsigned_long_long(
860    ctx: *mut RedisModuleInfoCtx,
861    name: &str,
862    value: c_ulonglong,
863) -> Status {
864    let name = CString::new(name).unwrap();
865    unsafe { RedisModule_InfoAddFieldULongLong.unwrap()(ctx, name.as_ptr(), value).into() }
866}
867
868#[allow(clippy::not_unsafe_ptr_arg_deref)]
869pub fn add_info_field_double(ctx: *mut RedisModuleInfoCtx, name: &str, value: c_double) -> Status {
870    let name = CString::new(name).unwrap();
871    unsafe { RedisModule_InfoAddFieldDouble.unwrap()(ctx, name.as_ptr(), value).into() }
872}
873
874#[allow(clippy::not_unsafe_ptr_arg_deref)]
875pub fn add_info_begin_dict_field(ctx: *mut RedisModuleInfoCtx, name: &str) -> Status {
876    let name = CString::new(name).unwrap();
877    unsafe { RedisModule_InfoBeginDictField.unwrap()(ctx, name.as_ptr()).into() }
878}
879
880#[allow(clippy::not_unsafe_ptr_arg_deref)]
881pub fn add_info_end_dict_field(ctx: *mut RedisModuleInfoCtx) -> Status {
882    unsafe { RedisModule_InfoEndDictField.unwrap()(ctx).into() }
883}
884
885/// # Safety
886///
887/// This function is safe to use as it doesn't perform any work with
888/// the [RedisModuleCtx] pointer except for passing it to the valkey server.
889///
890/// # Panics
891///
892/// Panics when the [RedisModule_ExportSharedAPI] is unavailable.
893pub unsafe fn export_shared_api(
894    ctx: *mut RedisModuleCtx,
895    func: *const ::std::os::raw::c_void,
896    name: *const ::std::os::raw::c_char,
897) {
898    RedisModule_ExportSharedAPI.unwrap()(ctx, name, func as *mut ::std::os::raw::c_void);
899}
900
901/// # Safety
902///
903/// This function is safe to use as it doesn't perform any work with
904/// the [RedisModuleCtx] pointer except for passing it to the valkey server.
905///
906/// # Panics
907///
908/// Panics when the [RedisModule_NotifyKeyspaceEvent] is unavailable.
909pub unsafe fn notify_keyspace_event(
910    ctx: *mut RedisModuleCtx,
911    event_type: NotifyEvent,
912    event: &str,
913    keyname: &ValkeyString,
914) -> Status {
915    let event = CString::new(event).unwrap();
916    RedisModule_NotifyKeyspaceEvent.unwrap()(ctx, event_type.bits(), event.as_ptr(), keyname.inner)
917        .into()
918}
919
920/// # Panics
921///
922/// Panics when the [RedisModule_GetNotifyKeyspaceEvents] is unavailable.
923#[must_use]
924pub fn get_keyspace_events() -> NotifyEvent {
925    unsafe {
926        let events = RedisModule_GetNotifyKeyspaceEvents.unwrap()();
927        NotifyEvent::from_bits_truncate(events)
928    }
929}
930
931/// Returns all the available notification flags for key-space
932/// notifications.
933///
934/// # Safety
935///
936/// This function is safe to use as it doesn't perform any work with
937/// the [RedisModuleCtx] pointer except for passing it to the valkey server.
938///
939/// # Panics
940///
941/// Panics when the [RedisModule_GetKeyspaceNotificationFlagsAll] is
942/// unavailable.
943pub fn get_keyspace_notification_flags_all() -> NotifyEvent {
944    unsafe {
945        NotifyEvent::from_bits_truncate(RedisModule_GetKeyspaceNotificationFlagsAll.unwrap()())
946    }
947}
948
949#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
950pub struct Version {
951    pub major: i32,
952    pub minor: i32,
953    pub patch: i32,
954}
955
956impl From<c_int> for Version {
957    fn from(ver: c_int) -> Self {
958        // Expected format: 0x00MMmmpp for Major, minor, patch
959        Self {
960            major: (ver & 0x00FF_0000) >> 16,
961            minor: (ver & 0x0000_FF00) >> 8,
962            patch: (ver & 0x0000_00FF),
963        }
964    }
965}
966
967#[allow(clippy::not_unsafe_ptr_arg_deref)]
968pub fn is_io_error(rdb: *mut RedisModuleIO) -> bool {
969    unsafe { RedisModule_IsIOError.unwrap()(rdb) != 0 }
970}