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///////////////////////////////////////////////////////////////
207
208pub const FMT: *const c_char = b"v\0".as_ptr().cast::<c_char>();
209
210// REDISMODULE_HASH_DELETE is defined explicitly here because bindgen cannot
211// parse typecasts in C macro constants yet.
212// See https://github.com/rust-lang/rust-bindgen/issues/316
213pub const REDISMODULE_HASH_DELETE: *const RedisModuleString = 1 as *const RedisModuleString;
214
215// Helper functions for the raw bindings.
216
217#[allow(clippy::not_unsafe_ptr_arg_deref)]
218pub fn call_reply_type(reply: *mut RedisModuleCallReply) -> ReplyType {
219    unsafe {
220        // TODO: Cache the unwrapped functions and use them instead of unwrapping every time?
221        RedisModule_CallReplyType.unwrap()(reply).into()
222    }
223}
224
225#[allow(clippy::not_unsafe_ptr_arg_deref)]
226pub fn free_call_reply(reply: *mut RedisModuleCallReply) {
227    unsafe { RedisModule_FreeCallReply.unwrap()(reply) }
228}
229
230#[allow(clippy::not_unsafe_ptr_arg_deref)]
231pub fn call_reply_integer(reply: *mut RedisModuleCallReply) -> c_longlong {
232    unsafe { RedisModule_CallReplyInteger.unwrap()(reply) }
233}
234
235/// # Panics
236///
237/// Panics if the Valkey server doesn't support replying with bool (since RESP3).
238#[allow(clippy::not_unsafe_ptr_arg_deref)]
239pub fn call_reply_bool(reply: *mut RedisModuleCallReply) -> bool {
240    (unsafe { RedisModule_CallReplyBool.unwrap()(reply) } != 0)
241}
242
243/// # Panics
244///
245/// Panics if the Valkey server doesn't support replying with bool (since RESP3).
246#[allow(clippy::not_unsafe_ptr_arg_deref)]
247pub fn call_reply_double(reply: *mut RedisModuleCallReply) -> f64 {
248    unsafe { RedisModule_CallReplyDouble.unwrap()(reply) }
249}
250
251/// # Panics
252///
253/// Panics if the Valkey server doesn't support replying with bool (since RESP3).
254#[allow(clippy::not_unsafe_ptr_arg_deref)]
255pub fn call_reply_big_number(reply: *mut RedisModuleCallReply) -> Option<String> {
256    unsafe {
257        let mut len: size_t = 0;
258        let reply_string: *mut u8 =
259            RedisModule_CallReplyBigNumber.unwrap()(reply, &mut len) as *mut u8;
260        if reply_string.is_null() {
261            return None;
262        }
263        String::from_utf8(slice::from_raw_parts(reply_string, len).to_vec()).ok()
264    }
265}
266
267/// # Panics
268///
269/// Panics if the Valkey server doesn't support replying with bool (since RESP3).
270#[allow(clippy::not_unsafe_ptr_arg_deref)]
271pub fn call_reply_verbatim_string(reply: *mut RedisModuleCallReply) -> Option<(String, Vec<u8>)> {
272    unsafe {
273        let mut len: size_t = 0;
274        let format: *const u8 = ptr::null();
275        let reply_string: *mut u8 =
276            RedisModule_CallReplyVerbatim.unwrap()(reply, &mut len, &mut (format as *const c_char))
277                as *mut u8;
278        if reply_string.is_null() {
279            return None;
280        }
281        Some((
282            String::from_utf8(slice::from_raw_parts(format, 3).to_vec()).ok()?,
283            slice::from_raw_parts(reply_string, len).to_vec(),
284        ))
285    }
286}
287
288#[allow(clippy::not_unsafe_ptr_arg_deref)]
289pub fn call_reply_array_element(
290    reply: *mut RedisModuleCallReply,
291    idx: usize,
292) -> *mut RedisModuleCallReply {
293    unsafe { RedisModule_CallReplyArrayElement.unwrap()(reply, idx) }
294}
295
296/// # Panics
297///
298/// Panics if the Valkey server doesn't support replying with bool (since RESP3).
299#[allow(clippy::not_unsafe_ptr_arg_deref)]
300pub fn call_reply_set_element(
301    reply: *mut RedisModuleCallReply,
302    idx: usize,
303) -> *mut RedisModuleCallReply {
304    unsafe { RedisModule_CallReplySetElement.unwrap()(reply, idx) }
305}
306
307/// # Panics
308///
309/// Panics if the Valkey server doesn't support replying with bool (since RESP3).
310#[allow(clippy::not_unsafe_ptr_arg_deref)]
311pub fn call_reply_map_element(
312    reply: *mut RedisModuleCallReply,
313    idx: usize,
314) -> (*mut RedisModuleCallReply, *mut RedisModuleCallReply) {
315    let mut key: *mut RedisModuleCallReply = ptr::null_mut();
316    let mut val: *mut RedisModuleCallReply = ptr::null_mut();
317    unsafe { RedisModule_CallReplyMapElement.unwrap()(reply, idx, &mut key, &mut val) };
318    (key, val)
319}
320
321#[allow(clippy::not_unsafe_ptr_arg_deref)]
322pub fn call_reply_length(reply: *mut RedisModuleCallReply) -> usize {
323    unsafe { RedisModule_CallReplyLength.unwrap()(reply) }
324}
325
326#[allow(clippy::not_unsafe_ptr_arg_deref)]
327pub fn call_reply_string_ptr(reply: *mut RedisModuleCallReply, len: *mut size_t) -> *const c_char {
328    unsafe { RedisModule_CallReplyStringPtr.unwrap()(reply, len) }
329}
330
331#[allow(clippy::not_unsafe_ptr_arg_deref)]
332pub fn call_reply_string(reply: *mut RedisModuleCallReply) -> Option<String> {
333    unsafe {
334        let mut len: size_t = 0;
335        let reply_string: *mut u8 =
336            RedisModule_CallReplyStringPtr.unwrap()(reply, &mut len) as *mut u8;
337        if reply_string.is_null() {
338            return None;
339        }
340        String::from_utf8(slice::from_raw_parts(reply_string, len).to_vec()).ok()
341    }
342}
343
344#[allow(clippy::not_unsafe_ptr_arg_deref)]
345#[inline]
346pub fn close_key(kp: *mut RedisModuleKey) {
347    unsafe { RedisModule_CloseKey.unwrap()(kp) }
348}
349
350#[allow(clippy::not_unsafe_ptr_arg_deref)]
351#[inline]
352pub fn open_key(
353    ctx: *mut RedisModuleCtx,
354    keyname: *mut RedisModuleString,
355    mode: KeyMode,
356) -> *mut RedisModuleKey {
357    unsafe { RedisModule_OpenKey.unwrap()(ctx, keyname, mode.bits()).cast::<RedisModuleKey>() }
358}
359
360#[allow(clippy::not_unsafe_ptr_arg_deref)]
361#[inline]
362pub(crate) fn open_key_with_flags(
363    ctx: *mut RedisModuleCtx,
364    keyname: *mut RedisModuleString,
365    mode: KeyMode,
366    flags: c_int,
367) -> *mut RedisModuleKey {
368    unsafe {
369        RedisModule_OpenKey.unwrap()(ctx, keyname, mode.bits() | flags).cast::<RedisModuleKey>()
370    }
371}
372
373#[allow(clippy::not_unsafe_ptr_arg_deref)]
374#[inline]
375pub fn reply_with_array(ctx: *mut RedisModuleCtx, len: c_long) -> Status {
376    unsafe { RedisModule_ReplyWithArray.unwrap()(ctx, len).into() }
377}
378
379#[allow(clippy::not_unsafe_ptr_arg_deref)]
380#[inline]
381pub fn reply_with_map(ctx: *mut RedisModuleCtx, len: c_long) -> Status {
382    unsafe {
383        RedisModule_ReplyWithMap
384            .map_or_else(
385                || RedisModule_ReplyWithArray.unwrap()(ctx, len * 2),
386                |f| f(ctx, len),
387            )
388            .into()
389    }
390}
391
392#[allow(clippy::not_unsafe_ptr_arg_deref)]
393#[inline]
394pub fn reply_with_set(ctx: *mut RedisModuleCtx, len: c_long) -> Status {
395    unsafe {
396        RedisModule_ReplyWithSet
397            .map_or_else(
398                || RedisModule_ReplyWithArray.unwrap()(ctx, len * 2),
399                |f| f(ctx, len),
400            )
401            .into()
402    }
403}
404
405#[allow(clippy::not_unsafe_ptr_arg_deref)]
406#[inline]
407pub fn reply_with_attribute(ctx: *mut RedisModuleCtx, len: c_long) -> Status {
408    unsafe { RedisModule_ReplyWithAttribute.unwrap()(ctx, len).into() }
409}
410
411#[allow(clippy::not_unsafe_ptr_arg_deref)]
412pub fn reply_with_error(ctx: *mut RedisModuleCtx, err: *const c_char) {
413    unsafe {
414        let msg = Context::str_as_legal_resp_string(CStr::from_ptr(err).to_str().unwrap());
415        RedisModule_ReplyWithError.unwrap()(ctx, msg.as_ptr());
416    }
417}
418
419#[allow(clippy::not_unsafe_ptr_arg_deref)]
420#[inline]
421pub fn reply_with_null(ctx: *mut RedisModuleCtx) -> Status {
422    unsafe { RedisModule_ReplyWithNull.unwrap()(ctx).into() }
423}
424
425#[allow(clippy::not_unsafe_ptr_arg_deref)]
426#[inline]
427pub fn reply_with_bool(ctx: *mut RedisModuleCtx, b: c_int) -> Status {
428    unsafe { RedisModule_ReplyWithBool.unwrap()(ctx, b).into() }
429}
430
431#[allow(clippy::not_unsafe_ptr_arg_deref)]
432#[inline]
433pub fn reply_with_long_long(ctx: *mut RedisModuleCtx, ll: c_longlong) -> Status {
434    unsafe { RedisModule_ReplyWithLongLong.unwrap()(ctx, ll).into() }
435}
436
437#[allow(clippy::not_unsafe_ptr_arg_deref)]
438#[inline]
439pub fn reply_with_double(ctx: *mut RedisModuleCtx, f: c_double) -> Status {
440    unsafe { RedisModule_ReplyWithDouble.unwrap()(ctx, f).into() }
441}
442
443#[allow(clippy::not_unsafe_ptr_arg_deref)]
444#[inline]
445pub fn reply_with_string(ctx: *mut RedisModuleCtx, s: *mut RedisModuleString) -> Status {
446    unsafe { RedisModule_ReplyWithString.unwrap()(ctx, s).into() }
447}
448
449#[allow(clippy::not_unsafe_ptr_arg_deref)]
450#[inline]
451pub fn reply_with_simple_string(ctx: *mut RedisModuleCtx, s: *const c_char) -> Status {
452    unsafe { RedisModule_ReplyWithSimpleString.unwrap()(ctx, s).into() }
453}
454
455#[allow(clippy::not_unsafe_ptr_arg_deref)]
456#[inline]
457pub fn reply_with_string_buffer(ctx: *mut RedisModuleCtx, s: *const c_char, len: size_t) -> Status {
458    unsafe { RedisModule_ReplyWithStringBuffer.unwrap()(ctx, s, len).into() }
459}
460
461#[allow(clippy::not_unsafe_ptr_arg_deref)]
462#[inline]
463pub fn reply_with_big_number(ctx: *mut RedisModuleCtx, s: *const c_char, len: size_t) -> Status {
464    unsafe { RedisModule_ReplyWithBigNumber.unwrap()(ctx, s, len).into() }
465}
466
467#[allow(clippy::not_unsafe_ptr_arg_deref)]
468#[inline]
469pub fn reply_with_verbatim_string(
470    ctx: *mut RedisModuleCtx,
471    s: *const c_char,
472    len: size_t,
473    format: *const c_char,
474) -> Status {
475    unsafe { RedisModule_ReplyWithVerbatimStringType.unwrap()(ctx, s, len, format).into() }
476}
477
478// Sets the expiry on a key.
479//
480// Expire is in milliseconds.
481#[allow(clippy::not_unsafe_ptr_arg_deref)]
482#[inline]
483pub fn set_expire(key: *mut RedisModuleKey, expire: c_longlong) -> Status {
484    unsafe { RedisModule_SetExpire.unwrap()(key, expire).into() }
485}
486
487#[allow(clippy::not_unsafe_ptr_arg_deref)]
488#[inline]
489pub fn string_dma(key: *mut RedisModuleKey, len: *mut size_t, mode: KeyMode) -> *mut c_char {
490    unsafe { RedisModule_StringDMA.unwrap()(key, len, mode.bits()) }
491}
492
493#[allow(clippy::not_unsafe_ptr_arg_deref)]
494#[inline]
495pub fn string_truncate(key: *mut RedisModuleKey, new_len: size_t) -> Status {
496    unsafe { RedisModule_StringTruncate.unwrap()(key, new_len).into() }
497}
498
499#[allow(clippy::not_unsafe_ptr_arg_deref)]
500pub fn hash_get_multi<T>(
501    key: *mut RedisModuleKey,
502    fields: &[T],
503    values: &mut [*mut RedisModuleString],
504) -> Result<(), ValkeyError>
505where
506    T: Into<Vec<u8>> + Clone,
507{
508    assert_eq!(fields.len(), values.len());
509
510    let fields = fields
511        .iter()
512        .map(|e| CString::new(e.clone()))
513        .collect::<Result<Vec<CString>, _>>()?;
514
515    let mut fi = fields.iter();
516    let mut vi = values.iter_mut();
517
518    macro_rules! rm {
519        () => { unsafe {
520            RedisModule_HashGet.unwrap()(key, REDISMODULE_HASH_CFIELDS as i32,
521                                         ptr::null::<c_char>())
522        }};
523        ($($args:expr)*) => { unsafe {
524            RedisModule_HashGet.unwrap()(
525                key, REDISMODULE_HASH_CFIELDS as i32,
526                $($args),*,
527                ptr::null::<c_char>()
528            )
529        }};
530    }
531    macro_rules! f {
532        () => {
533            fi.next().unwrap().as_ptr()
534        };
535    }
536    macro_rules! v {
537        () => {
538            vi.next().unwrap()
539        };
540    }
541
542    // This convoluted code is necessary since Redis only exposes a varargs API for HashGet
543    // to modules. Unfortunately there's no straightforward or portable way of calling a
544    // a varargs function with a variable number of arguments that is determined at runtime.
545    // See also the following Redis ticket: https://github.com/redis/redis/issues/7860
546    let res = Status::from(match fields.len() {
547        0 => rm! {},
548        1 => rm! {f!() v!()},
549        2 => rm! {f!() v!() f!() v!()},
550        3 => rm! {f!() v!() f!() v!() f!() v!()},
551        4 => rm! {f!() v!() f!() v!() f!() v!() f!() v!()},
552        5 => rm! {f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()},
553        6 => rm! {f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()},
554        7 => rm! {
555            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
556            f!() v!()
557        },
558        8 => rm! {
559            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
560            f!() v!() f!() v!()
561        },
562        9 => rm! {
563            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
564            f!() v!() f!() v!() f!() v!()
565        },
566        10 => rm! {
567            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
568            f!() v!() f!() v!() f!() v!() f!() v!()
569        },
570        11 => rm! {
571            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
572            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
573        },
574        12 => rm! {
575            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
576            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
577        },
578        _ => panic!("Unsupported length"),
579    });
580
581    match res {
582        Status::Ok => Ok(()),
583        Status::Err => Err(ValkeyError::Str("ERR key is not a hash value")),
584    }
585}
586
587#[allow(clippy::not_unsafe_ptr_arg_deref)]
588#[inline]
589pub fn hash_set(key: *mut RedisModuleKey, field: &str, value: *mut RedisModuleString) -> Status {
590    let field = CString::new(field).unwrap();
591
592    unsafe {
593        RedisModule_HashSet.unwrap()(
594            key,
595            REDISMODULE_HASH_CFIELDS as i32,
596            field.as_ptr(),
597            value,
598            ptr::null::<c_char>(),
599        )
600        .into()
601    }
602}
603
604#[allow(clippy::not_unsafe_ptr_arg_deref)]
605#[inline]
606pub fn hash_del(key: *mut RedisModuleKey, field: &str) -> Status {
607    let field = CString::new(field).unwrap();
608
609    // TODO: Add hash_del_multi()
610    // Support to pass multiple fields is desired but is complicated.
611    // See hash_get_multi() and https://github.com/redis/redis/issues/7860
612
613    unsafe {
614        RedisModule_HashSet.unwrap()(
615            key,
616            REDISMODULE_HASH_CFIELDS as i32,
617            field.as_ptr(),
618            REDISMODULE_HASH_DELETE,
619            ptr::null::<c_char>(),
620        )
621        .into()
622    }
623}
624
625#[allow(clippy::not_unsafe_ptr_arg_deref)]
626#[inline]
627pub fn list_push(
628    key: *mut RedisModuleKey,
629    list_where: Where,
630    element: *mut RedisModuleString,
631) -> Status {
632    unsafe { RedisModule_ListPush.unwrap()(key, list_where as i32, element).into() }
633}
634
635#[allow(clippy::not_unsafe_ptr_arg_deref)]
636#[inline]
637pub fn list_pop(key: *mut RedisModuleKey, list_where: Where) -> *mut RedisModuleString {
638    unsafe { RedisModule_ListPop.unwrap()(key, list_where as i32) }
639}
640
641// Returns pointer to the C string, and sets len to its length
642#[allow(clippy::not_unsafe_ptr_arg_deref)]
643#[inline]
644pub fn string_ptr_len(s: *const RedisModuleString, len: *mut size_t) -> *const c_char {
645    unsafe { RedisModule_StringPtrLen.unwrap()(s, len) }
646}
647
648#[allow(clippy::not_unsafe_ptr_arg_deref)]
649#[inline]
650pub fn string_retain_string(ctx: *mut RedisModuleCtx, s: *mut RedisModuleString) {
651    unsafe { RedisModule_RetainString.unwrap()(ctx, s) }
652}
653
654#[allow(clippy::not_unsafe_ptr_arg_deref)]
655#[inline]
656pub fn string_to_longlong(s: *const RedisModuleString, len: *mut i64) -> Status {
657    unsafe { RedisModule_StringToLongLong.unwrap()(s, len).into() }
658}
659
660#[allow(clippy::not_unsafe_ptr_arg_deref)]
661#[inline]
662pub fn string_to_double(s: *const RedisModuleString, len: *mut f64) -> Status {
663    unsafe { RedisModule_StringToDouble.unwrap()(s, len).into() }
664}
665
666#[allow(clippy::not_unsafe_ptr_arg_deref)]
667#[inline]
668pub fn string_set(key: *mut RedisModuleKey, s: *mut RedisModuleString) -> Status {
669    unsafe { RedisModule_StringSet.unwrap()(key, s).into() }
670}
671
672#[allow(clippy::not_unsafe_ptr_arg_deref)]
673#[inline]
674pub fn replicate_verbatim(ctx: *mut RedisModuleCtx) -> Status {
675    unsafe { RedisModule_ReplicateVerbatim.unwrap()(ctx).into() }
676}
677
678fn load<F, T>(rdb: *mut RedisModuleIO, f: F) -> Result<T, Error>
679where
680    F: FnOnce(*mut RedisModuleIO) -> T,
681{
682    let res = f(rdb);
683    if is_io_error(rdb) {
684        Err(ValkeyError::short_read().into())
685    } else {
686        Ok(res)
687    }
688}
689
690#[allow(clippy::not_unsafe_ptr_arg_deref)]
691pub fn load_unsigned(rdb: *mut RedisModuleIO) -> Result<u64, Error> {
692    unsafe { load(rdb, |rdb| RedisModule_LoadUnsigned.unwrap()(rdb)) }
693}
694
695#[allow(clippy::not_unsafe_ptr_arg_deref)]
696pub fn load_signed(rdb: *mut RedisModuleIO) -> Result<i64, Error> {
697    unsafe { load(rdb, |rdb| RedisModule_LoadSigned.unwrap()(rdb)) }
698}
699
700#[allow(clippy::not_unsafe_ptr_arg_deref)]
701pub fn load_string(rdb: *mut RedisModuleIO) -> Result<ValkeyString, Error> {
702    let p = unsafe { load(rdb, |rdb| RedisModule_LoadString.unwrap()(rdb))? };
703    Ok(ValkeyString::from_redis_module_string(ptr::null_mut(), p))
704}
705
706#[allow(clippy::not_unsafe_ptr_arg_deref)]
707pub fn load_string_buffer(rdb: *mut RedisModuleIO) -> Result<RedisBuffer, Error> {
708    unsafe {
709        let mut len = 0;
710        let buffer = load(rdb, |rdb| {
711            RedisModule_LoadStringBuffer.unwrap()(rdb, &mut len)
712        })?;
713        Ok(RedisBuffer::new(buffer, len))
714    }
715}
716
717#[allow(clippy::not_unsafe_ptr_arg_deref)]
718pub fn replicate<'a, T: Into<StrCallArgs<'a>>>(
719    ctx: *mut RedisModuleCtx,
720    command: &str,
721    args: T,
722) -> Status {
723    let mut call_args: StrCallArgs = args.into();
724    let final_args = call_args.args_mut();
725
726    let cmd = CString::new(command).unwrap();
727
728    unsafe {
729        RedisModule_Replicate.unwrap()(
730            ctx,
731            cmd.as_ptr(),
732            FMT,
733            final_args.as_ptr(),
734            final_args.len(),
735        )
736        .into()
737    }
738}
739
740#[allow(clippy::not_unsafe_ptr_arg_deref)]
741pub fn load_double(rdb: *mut RedisModuleIO) -> Result<f64, Error> {
742    unsafe { load(rdb, |rdb| RedisModule_LoadDouble.unwrap()(rdb)) }
743}
744
745#[allow(clippy::not_unsafe_ptr_arg_deref)]
746pub fn load_float(rdb: *mut RedisModuleIO) -> Result<f32, Error> {
747    unsafe { load(rdb, |rdb| RedisModule_LoadFloat.unwrap()(rdb)) }
748}
749
750#[allow(clippy::not_unsafe_ptr_arg_deref)]
751pub fn save_string(rdb: *mut RedisModuleIO, buf: &str) {
752    unsafe { RedisModule_SaveStringBuffer.unwrap()(rdb, buf.as_ptr().cast::<c_char>(), buf.len()) };
753}
754
755#[allow(clippy::not_unsafe_ptr_arg_deref)]
756/// Save the `RedisString` into the RDB
757pub fn save_redis_string(rdb: *mut RedisModuleIO, s: &ValkeyString) {
758    unsafe { RedisModule_SaveString.unwrap()(rdb, s.inner) };
759}
760
761#[allow(clippy::not_unsafe_ptr_arg_deref)]
762/// Save the `&[u8]` into the RDB
763pub fn save_slice(rdb: *mut RedisModuleIO, buf: &[u8]) {
764    unsafe { RedisModule_SaveStringBuffer.unwrap()(rdb, buf.as_ptr().cast::<c_char>(), buf.len()) };
765}
766
767#[allow(clippy::not_unsafe_ptr_arg_deref)]
768pub fn save_double(rdb: *mut RedisModuleIO, val: f64) {
769    unsafe { RedisModule_SaveDouble.unwrap()(rdb, val) };
770}
771
772#[allow(clippy::not_unsafe_ptr_arg_deref)]
773pub fn save_signed(rdb: *mut RedisModuleIO, val: i64) {
774    unsafe { RedisModule_SaveSigned.unwrap()(rdb, val) };
775}
776
777#[allow(clippy::not_unsafe_ptr_arg_deref)]
778pub fn save_float(rdb: *mut RedisModuleIO, val: f32) {
779    unsafe { RedisModule_SaveFloat.unwrap()(rdb, val) };
780}
781
782#[allow(clippy::not_unsafe_ptr_arg_deref)]
783pub fn save_unsigned(rdb: *mut RedisModuleIO, val: u64) {
784    unsafe { RedisModule_SaveUnsigned.unwrap()(rdb, val) };
785}
786
787#[allow(clippy::not_unsafe_ptr_arg_deref)]
788pub fn string_compare(a: *mut RedisModuleString, b: *mut RedisModuleString) -> Ordering {
789    unsafe { RedisModule_StringCompare.unwrap()(a, b).cmp(&0) }
790}
791
792#[allow(clippy::not_unsafe_ptr_arg_deref)]
793pub fn string_append_buffer(
794    ctx: *mut RedisModuleCtx,
795    s: *mut RedisModuleString,
796    buff: &str,
797) -> Status {
798    unsafe {
799        RedisModule_StringAppendBuffer.unwrap()(ctx, s, buff.as_ptr().cast::<c_char>(), buff.len())
800            .into()
801    }
802}
803
804#[allow(clippy::not_unsafe_ptr_arg_deref)]
805pub fn subscribe_to_server_event(
806    ctx: *mut RedisModuleCtx,
807    event: RedisModuleEvent,
808    callback: RedisModuleEventCallback,
809) -> Status {
810    unsafe { RedisModule_SubscribeToServerEvent.unwrap()(ctx, event, callback).into() }
811}
812
813#[allow(clippy::not_unsafe_ptr_arg_deref)]
814pub fn register_info_function(ctx: *mut RedisModuleCtx, callback: RedisModuleInfoFunc) -> Status {
815    unsafe { RedisModule_RegisterInfoFunc.unwrap()(ctx, callback).into() }
816}
817
818#[allow(clippy::not_unsafe_ptr_arg_deref)]
819pub fn add_info_section(ctx: *mut RedisModuleInfoCtx, name: Option<&str>) -> Status {
820    name.map(|n| CString::new(n).unwrap()).map_or_else(
821        || unsafe { RedisModule_InfoAddSection.unwrap()(ctx, ptr::null_mut()).into() },
822        |n| unsafe { RedisModule_InfoAddSection.unwrap()(ctx, n.as_ptr()).into() },
823    )
824}
825
826#[allow(clippy::not_unsafe_ptr_arg_deref)]
827pub fn add_info_field_str(ctx: *mut RedisModuleInfoCtx, name: &str, content: &str) -> Status {
828    let name = CString::new(name).unwrap();
829    let content = ValkeyString::create(None, content);
830    unsafe { RedisModule_InfoAddFieldString.unwrap()(ctx, name.as_ptr(), content.inner).into() }
831}
832
833#[allow(clippy::not_unsafe_ptr_arg_deref)]
834pub fn add_info_field_long_long(
835    ctx: *mut RedisModuleInfoCtx,
836    name: &str,
837    value: c_longlong,
838) -> Status {
839    let name = CString::new(name).unwrap();
840    unsafe { RedisModule_InfoAddFieldLongLong.unwrap()(ctx, name.as_ptr(), value).into() }
841}
842
843#[allow(clippy::not_unsafe_ptr_arg_deref)]
844pub fn add_info_field_unsigned_long_long(
845    ctx: *mut RedisModuleInfoCtx,
846    name: &str,
847    value: c_ulonglong,
848) -> Status {
849    let name = CString::new(name).unwrap();
850    unsafe { RedisModule_InfoAddFieldULongLong.unwrap()(ctx, name.as_ptr(), value).into() }
851}
852
853#[allow(clippy::not_unsafe_ptr_arg_deref)]
854pub fn add_info_field_double(ctx: *mut RedisModuleInfoCtx, name: &str, value: c_double) -> Status {
855    let name = CString::new(name).unwrap();
856    unsafe { RedisModule_InfoAddFieldDouble.unwrap()(ctx, name.as_ptr(), value).into() }
857}
858
859#[allow(clippy::not_unsafe_ptr_arg_deref)]
860pub fn add_info_begin_dict_field(ctx: *mut RedisModuleInfoCtx, name: &str) -> Status {
861    let name = CString::new(name).unwrap();
862    unsafe { RedisModule_InfoBeginDictField.unwrap()(ctx, name.as_ptr()).into() }
863}
864
865#[allow(clippy::not_unsafe_ptr_arg_deref)]
866pub fn add_info_end_dict_field(ctx: *mut RedisModuleInfoCtx) -> Status {
867    unsafe { RedisModule_InfoEndDictField.unwrap()(ctx).into() }
868}
869
870/// # Safety
871///
872/// This function is safe to use as it doesn't perform any work with
873/// the [RedisModuleCtx] pointer except for passing it to the valkey server.
874///
875/// # Panics
876///
877/// Panics when the [RedisModule_ExportSharedAPI] is unavailable.
878pub unsafe fn export_shared_api(
879    ctx: *mut RedisModuleCtx,
880    func: *const ::std::os::raw::c_void,
881    name: *const ::std::os::raw::c_char,
882) {
883    RedisModule_ExportSharedAPI.unwrap()(ctx, name, func as *mut ::std::os::raw::c_void);
884}
885
886/// # Safety
887///
888/// This function is safe to use as it doesn't perform any work with
889/// the [RedisModuleCtx] pointer except for passing it to the valkey server.
890///
891/// # Panics
892///
893/// Panics when the [RedisModule_NotifyKeyspaceEvent] is unavailable.
894pub unsafe fn notify_keyspace_event(
895    ctx: *mut RedisModuleCtx,
896    event_type: NotifyEvent,
897    event: &str,
898    keyname: &ValkeyString,
899) -> Status {
900    let event = CString::new(event).unwrap();
901    RedisModule_NotifyKeyspaceEvent.unwrap()(ctx, event_type.bits(), event.as_ptr(), keyname.inner)
902        .into()
903}
904
905/// # Panics
906///
907/// Panics when the [RedisModule_GetNotifyKeyspaceEvents] is unavailable.
908#[must_use]
909pub fn get_keyspace_events() -> NotifyEvent {
910    unsafe {
911        let events = RedisModule_GetNotifyKeyspaceEvents.unwrap()();
912        NotifyEvent::from_bits_truncate(events)
913    }
914}
915
916/// Returns all the available notification flags for key-space
917/// notifications.
918///
919/// # Safety
920///
921/// This function is safe to use as it doesn't perform any work with
922/// the [RedisModuleCtx] pointer except for passing it to the valkey server.
923///
924/// # Panics
925///
926/// Panics when the [RedisModule_GetKeyspaceNotificationFlagsAll] is
927/// unavailable.
928pub fn get_keyspace_notification_flags_all() -> NotifyEvent {
929    unsafe {
930        NotifyEvent::from_bits_truncate(RedisModule_GetKeyspaceNotificationFlagsAll.unwrap()())
931    }
932}
933
934#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
935pub struct Version {
936    pub major: i32,
937    pub minor: i32,
938    pub patch: i32,
939}
940
941impl From<c_int> for Version {
942    fn from(ver: c_int) -> Self {
943        // Expected format: 0x00MMmmpp for Major, minor, patch
944        Self {
945            major: (ver & 0x00FF_0000) >> 16,
946            minor: (ver & 0x0000_FF00) >> 8,
947            patch: (ver & 0x0000_00FF),
948        }
949    }
950}
951
952#[allow(clippy::not_unsafe_ptr_arg_deref)]
953pub fn is_io_error(rdb: *mut RedisModuleIO) -> bool {
954    unsafe { RedisModule_IsIOError.unwrap()(rdb) != 0 }
955}