Skip to main content

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