Skip to main content

redis_module/context/
commands.rs

1use crate::raw;
2use crate::Context;
3use crate::RedisError;
4use crate::Status;
5use bitflags::bitflags;
6use libc::c_char;
7use linkme::distributed_slice;
8use redis_module_macros_internals::api;
9use std::ffi::CString;
10use std::iter;
11use std::mem::MaybeUninit;
12use std::os::raw::c_int;
13use std::ptr;
14
15const COMMNAD_INFO_VERSION: raw::RedisModuleCommandInfoVersion =
16    raw::RedisModuleCommandInfoVersion {
17        version: 1,
18        sizeof_historyentry: std::mem::size_of::<raw::RedisModuleCommandHistoryEntry>(),
19        sizeof_keyspec: std::mem::size_of::<raw::RedisModuleCommandKeySpec>(),
20        sizeof_arg: std::mem::size_of::<raw::RedisModuleCommandArg>(),
21    };
22
23bitflags! {
24    /// Key spec flags
25    ///
26    /// The first four refer to what the command actually does with the value or
27    /// metadata of the key, and not necessarily the user data or how it affects
28    /// it. Each key-spec must have exactly one of these. Any operation
29    /// that's not distinctly deletion, overwrite or read-only would be marked as
30    /// RW.
31    ///
32    /// The next four refer to user data inside the value of the key, not the
33    /// metadata like LRU, type, cardinality. It refers to the logical operation
34    /// on the user's data (actual input strings or TTL), being
35    /// used/returned/copied/changed. It doesn't refer to modification or
36    /// returning of metadata (like type, count, presence of data). ACCESS can be
37    /// combined with one of the write operations INSERT, DELETE or UPDATE. Any
38    /// write that's not an INSERT or a DELETE would be UPDATE.
39    pub struct KeySpecFlags : u32 {
40        /// Read-Only. Reads the value of the key, but doesn't necessarily return it.
41        const READ_ONLY = raw::REDISMODULE_CMD_KEY_RO;
42
43        /// Read-Write. Modifies the data stored in the value of the key or its metadata.
44        const READ_WRITE = raw::REDISMODULE_CMD_KEY_RW;
45
46        /// Overwrite. Overwrites the data stored in the value of the key.
47        const OVERWRITE = raw::REDISMODULE_CMD_KEY_OW;
48
49        /// Deletes the key.
50        const REMOVE = raw::REDISMODULE_CMD_KEY_RM;
51
52        /// Returns, copies or uses the user data from the value of the key.
53        const ACCESS = raw::REDISMODULE_CMD_KEY_ACCESS;
54
55        /// Updates data to the value, new value may depend on the old value.
56        const UPDATE = raw::REDISMODULE_CMD_KEY_UPDATE;
57
58        /// Adds data to the value with no chance of modification or deletion of existing data.
59        const INSERT = raw::REDISMODULE_CMD_KEY_INSERT;
60
61        /// Explicitly deletes some content from the value of the key.
62        const DELETE = raw::REDISMODULE_CMD_KEY_DELETE;
63
64        /// The key is not actually a key, but should be routed in cluster mode as if it was a key.
65        const NOT_KEY = raw::REDISMODULE_CMD_KEY_NOT_KEY;
66
67        /// The keyspec might not point out all the keys it should cover.
68        const INCOMPLETE = raw::REDISMODULE_CMD_KEY_INCOMPLETE;
69
70        /// Some keys might have different flags depending on arguments.
71        const VARIABLE_FLAGS = raw::REDISMODULE_CMD_KEY_VARIABLE_FLAGS;
72    }
73}
74
75impl TryFrom<&str> for KeySpecFlags {
76    type Error = RedisError;
77    fn try_from(value: &str) -> Result<Self, Self::Error> {
78        match value.to_lowercase().as_str() {
79            "read_only" => Ok(KeySpecFlags::READ_ONLY),
80            "read_write" => Ok(KeySpecFlags::READ_WRITE),
81            "overwrite" => Ok(KeySpecFlags::OVERWRITE),
82            "remove" => Ok(KeySpecFlags::REMOVE),
83            "access" => Ok(KeySpecFlags::ACCESS),
84            "update" => Ok(KeySpecFlags::UPDATE),
85            "insert" => Ok(KeySpecFlags::INSERT),
86            "delete" => Ok(KeySpecFlags::DELETE),
87            "not_key" => Ok(KeySpecFlags::NOT_KEY),
88            "incomplete" => Ok(KeySpecFlags::INCOMPLETE),
89            "variable_flags" => Ok(KeySpecFlags::VARIABLE_FLAGS),
90            _ => Err(RedisError::String(format!(
91                "Value {value} is not a valid key spec flag."
92            ))),
93        }
94    }
95}
96
97impl From<Vec<KeySpecFlags>> for KeySpecFlags {
98    fn from(value: Vec<KeySpecFlags>) -> Self {
99        value
100            .into_iter()
101            .fold(KeySpecFlags::empty(), |a, item| a | item)
102    }
103}
104
105/// A version of begin search spec that finds the index
106/// indicating where to start search for keys based on
107/// an index.
108pub struct BeginSearchIndex {
109    index: i32,
110}
111
112/// A version of begin search spec that finds the index
113/// indicating where to start search for keys based on
114/// a keyword.
115pub struct BeginSearchKeyword {
116    keyword: String,
117    startfrom: i32,
118}
119
120/// This struct represents how Redis should start looking for keys.
121/// There are 2 possible options:
122/// 1. Index - start looking for keys from a given position.
123/// 2. Keyword - Search for a specific keyward and start looking for keys from this keyword
124pub enum BeginSearch {
125    Index(BeginSearchIndex),
126    Keyword(BeginSearchKeyword),
127}
128
129impl BeginSearch {
130    pub fn new_index(index: i32) -> BeginSearch {
131        BeginSearch::Index(BeginSearchIndex { index })
132    }
133
134    pub fn new_keyword(keyword: String, startfrom: i32) -> BeginSearch {
135        BeginSearch::Keyword(BeginSearchKeyword { keyword, startfrom })
136    }
137}
138
139impl From<&BeginSearch>
140    for (
141        raw::RedisModuleKeySpecBeginSearchType,
142        raw::RedisModuleCommandKeySpec__bindgen_ty_1,
143    )
144{
145    fn from(value: &BeginSearch) -> Self {
146        match value {
147            BeginSearch::Index(index_spec) => (
148                raw::RedisModuleKeySpecBeginSearchType_REDISMODULE_KSPEC_BS_INDEX,
149                raw::RedisModuleCommandKeySpec__bindgen_ty_1 {
150                    index: raw::RedisModuleCommandKeySpec__bindgen_ty_1__bindgen_ty_1 {
151                        pos: index_spec.index,
152                    },
153                },
154            ),
155            BeginSearch::Keyword(keyword_spec) => {
156                let keyword = CString::new(keyword_spec.keyword.as_str())
157                    .unwrap()
158                    .into_raw();
159                (
160                    raw::RedisModuleKeySpecBeginSearchType_REDISMODULE_KSPEC_BS_KEYWORD,
161                    raw::RedisModuleCommandKeySpec__bindgen_ty_1 {
162                        keyword: raw::RedisModuleCommandKeySpec__bindgen_ty_1__bindgen_ty_2 {
163                            keyword,
164                            startfrom: keyword_spec.startfrom,
165                        },
166                    },
167                )
168            }
169        }
170    }
171}
172
173/// A version of find keys base on range.
174/// * `last_key` - Index of the last key relative to the result of the
175///   begin search step. Can be negative, in which case it's not
176///   relative. -1 indicates the last argument, -2 one before the
177///   last and so on.
178/// * `steps` - How many arguments should we skip after finding a
179///   key, in order to find the next one.
180/// * `limit` - If `lastkey` is -1, we use `limit` to stop the search
181///   by a factor. 0 and 1 mean no limit. 2 means 1/2 of the
182///   remaining args, 3 means 1/3, and so on.
183pub struct FindKeysRange {
184    last_key: i32,
185    steps: i32,
186    limit: i32,
187}
188
189/// A version of find keys base on some argument representing the number of keys
190/// * keynumidx - Index of the argument containing the number of
191///   keys to come, relative to the result of the begin search step.
192/// * firstkey - Index of the fist key relative to the result of the
193///   begin search step. (Usually it's just after `keynumidx`, in
194///   which case it should be set to `keynumidx + 1`.)
195/// * keystep - How many arguments should we skip after finding a
196///   key, in order to find the next one?
197pub struct FindKeysNum {
198    key_num_idx: i32,
199    first_key: i32,
200    key_step: i32,
201}
202
203/// After Redis finds the location from where it needs to start looking for keys,
204/// Redis will start finding keys base on the information in this enum.
205/// There are 2 possible options:
206/// 1. Range - Required to specify additional 3 more values, `last_key`, `steps`, and `limit`.
207/// 2. Keynum - Required to specify additional 3 more values, `keynumidx`, `firstkey`, and `keystep`.
208///    Redis will consider the argument at `keynumidx` as an indicator
209///    to the number of keys that will follow. Then it will start
210///    from `firstkey` and jump each `keystep` to find the keys.
211pub enum FindKeys {
212    Range(FindKeysRange),
213    Keynum(FindKeysNum),
214}
215
216impl FindKeys {
217    pub fn new_range(last_key: i32, steps: i32, limit: i32) -> FindKeys {
218        FindKeys::Range(FindKeysRange {
219            last_key,
220            steps,
221            limit,
222        })
223    }
224
225    pub fn new_keys_num(key_num_idx: i32, first_key: i32, key_step: i32) -> FindKeys {
226        FindKeys::Keynum(FindKeysNum {
227            key_num_idx,
228            first_key,
229            key_step,
230        })
231    }
232}
233
234impl From<&FindKeys>
235    for (
236        raw::RedisModuleKeySpecFindKeysType,
237        raw::RedisModuleCommandKeySpec__bindgen_ty_2,
238    )
239{
240    fn from(value: &FindKeys) -> Self {
241        match value {
242            FindKeys::Range(range_spec) => (
243                raw::RedisModuleKeySpecFindKeysType_REDISMODULE_KSPEC_FK_RANGE,
244                raw::RedisModuleCommandKeySpec__bindgen_ty_2 {
245                    range: raw::RedisModuleCommandKeySpec__bindgen_ty_2__bindgen_ty_1 {
246                        lastkey: range_spec.last_key,
247                        keystep: range_spec.steps,
248                        limit: range_spec.limit,
249                    },
250                },
251            ),
252            FindKeys::Keynum(keynum_spec) => (
253                raw::RedisModuleKeySpecFindKeysType_REDISMODULE_KSPEC_FK_KEYNUM,
254                raw::RedisModuleCommandKeySpec__bindgen_ty_2 {
255                    keynum: raw::RedisModuleCommandKeySpec__bindgen_ty_2__bindgen_ty_2 {
256                        keynumidx: keynum_spec.key_num_idx,
257                        firstkey: keynum_spec.first_key,
258                        keystep: keynum_spec.key_step,
259                    },
260                },
261            ),
262        }
263    }
264}
265
266/// A struct that specify how to find keys from a command.
267/// It is devided into 2 parts:
268/// 1. begin_search - indicate how to find the first command argument from where to start searching for keys.
269/// 2. find_keys - the methose to use in order to find the keys.
270pub struct KeySpec {
271    notes: Option<String>,
272    flags: KeySpecFlags,
273    begin_search: BeginSearch,
274    find_keys: FindKeys,
275}
276
277impl KeySpec {
278    pub fn new(
279        notes: Option<String>,
280        flags: KeySpecFlags,
281        begin_search: BeginSearch,
282        find_keys: FindKeys,
283    ) -> KeySpec {
284        KeySpec {
285            notes,
286            flags,
287            begin_search,
288            find_keys,
289        }
290    }
291}
292
293impl From<&KeySpec> for raw::RedisModuleCommandKeySpec {
294    fn from(value: &KeySpec) -> Self {
295        let (begin_search_type, bs) = (&value.begin_search).into();
296        let (find_keys_type, fk) = (&value.find_keys).into();
297        raw::RedisModuleCommandKeySpec {
298            notes: value
299                .notes
300                .as_ref()
301                .map(|v| CString::new(v.as_str()).unwrap().into_raw())
302                .unwrap_or(ptr::null_mut()),
303            flags: value.flags.bits() as u64,
304            begin_search_type,
305            bs,
306            find_keys_type,
307            fk,
308        }
309    }
310}
311
312type CommandCallback =
313    extern "C" fn(*mut raw::RedisModuleCtx, *mut *mut raw::RedisModuleString, i32) -> i32;
314
315bitflags! {
316    pub struct CommandArgFlags : u32 {
317        const NONE = raw::REDISMODULE_CMD_ARG_NONE;
318        const OPTIONAL = raw::REDISMODULE_CMD_ARG_OPTIONAL;
319        const MULTIPLE = raw::REDISMODULE_CMD_ARG_MULTIPLE;
320        const MULTIPLE_TOKEN = raw::REDISMODULE_CMD_ARG_MULTIPLE_TOKEN;
321    }
322}
323
324impl TryFrom<&str> for CommandArgFlags {
325    type Error = RedisError;
326    fn try_from(value: &str) -> Result<Self, Self::Error> {
327        match value.to_lowercase().as_str() {
328            "none" => Ok(CommandArgFlags::NONE),
329            "optional" => Ok(CommandArgFlags::OPTIONAL),
330            "multiple" => Ok(CommandArgFlags::MULTIPLE),
331            "multiple_token" => Ok(CommandArgFlags::MULTIPLE_TOKEN),
332            _ => Err(RedisError::String(format!(
333                "Value {value} is not a valid command arg flag."
334            ))),
335        }
336    }
337}
338
339impl From<Vec<CommandArgFlags>> for CommandArgFlags {
340    fn from(value: Vec<CommandArgFlags>) -> Self {
341        value
342            .into_iter()
343            .fold(CommandArgFlags::empty(), |a, item| a | item)
344    }
345}
346
347pub struct RedisModuleCommandArg {
348    name: String,
349    type_: u32,
350    key_spec_index: Option<u32>,
351    token: Option<String>,
352    summary: Option<String>,
353    since: Option<String>,
354    flags: CommandArgFlags,
355    deprecated_since: Option<String>,
356    subargs: Option<Vec<RedisModuleCommandArg>>,
357    display_text: Option<String>,
358}
359
360impl RedisModuleCommandArg {
361    #[expect(clippy::too_many_arguments)]
362    pub fn new(
363        name: String,
364        type_: u32,
365        key_spec_index: Option<u32>,
366        token: Option<String>,
367        summary: Option<String>,
368        since: Option<String>,
369        flags: CommandArgFlags,
370        deprecated_since: Option<String>,
371        subargs: Option<Vec<RedisModuleCommandArg>>,
372        display_text: Option<String>,
373    ) -> RedisModuleCommandArg {
374        RedisModuleCommandArg {
375            name,
376            type_,
377            key_spec_index,
378            token,
379            summary,
380            since,
381            flags,
382            deprecated_since,
383            subargs,
384            display_text,
385        }
386    }
387}
388
389/// A struct represent a CommandInfo
390pub struct CommandInfo {
391    name: String,
392    flags: Option<String>,
393    enterprise_flags: Option<String>,
394    summary: Option<String>,
395    complexity: Option<String>,
396    since: Option<String>,
397    tips: Option<String>,
398    arity: i64,
399    key_spec: Vec<KeySpec>,
400    callback: CommandCallback,
401    args: Vec<RedisModuleCommandArg>,
402    acl_categories: Option<Vec<String>>,
403}
404
405impl CommandInfo {
406    #[expect(clippy::too_many_arguments)]
407    pub fn new(
408        name: String,
409        flags: Option<String>,
410        enterprise_flags: Option<String>,
411        summary: Option<String>,
412        complexity: Option<String>,
413        since: Option<String>,
414        tips: Option<String>,
415        arity: i64,
416        key_spec: Vec<KeySpec>,
417        callback: CommandCallback,
418        args: Vec<RedisModuleCommandArg>,
419        acl_categories: Option<Vec<String>>,
420    ) -> CommandInfo {
421        CommandInfo {
422            name,
423            flags,
424            enterprise_flags,
425            summary,
426            complexity,
427            since,
428            tips,
429            arity,
430            key_spec,
431            callback,
432            args,
433            acl_categories,
434        }
435    }
436}
437
438#[distributed_slice()]
439pub static COMMANDS_LIST: [fn() -> Result<CommandInfo, RedisError>] = [..];
440
441pub fn get_redis_key_spec(key_spec: Vec<KeySpec>) -> Vec<raw::RedisModuleCommandKeySpec> {
442    let mut redis_key_spec: Vec<raw::RedisModuleCommandKeySpec> =
443        key_spec.into_iter().map(|v| (&v).into()).collect();
444    let zerod: raw::RedisModuleCommandKeySpec = unsafe { MaybeUninit::zeroed().assume_init() };
445    redis_key_spec.push(zerod);
446    redis_key_spec
447}
448
449fn convert_command_arg_to_raw(arg: &RedisModuleCommandArg) -> raw::RedisModuleCommandArg {
450    let name = CString::new(arg.name.as_str()).unwrap().into_raw();
451    let token = arg
452        .token
453        .as_ref()
454        .map(|v| CString::new(v.as_str()).unwrap().into_raw())
455        .unwrap_or(ptr::null_mut());
456    let summary = arg
457        .summary
458        .as_ref()
459        .map(|v| CString::new(v.as_str()).unwrap().into_raw())
460        .unwrap_or(ptr::null_mut());
461    let since = arg
462        .since
463        .as_ref()
464        .map(|v| CString::new(v.as_str()).unwrap().into_raw())
465        .unwrap_or(ptr::null_mut());
466    let deprecated_since = arg
467        .deprecated_since
468        .as_ref()
469        .map(|v| CString::new(v.as_str()).unwrap().into_raw())
470        .unwrap_or(ptr::null_mut());
471    let display_text = arg
472        .display_text
473        .as_ref()
474        .map(|v| CString::new(v.as_str()).unwrap().into_raw())
475        .unwrap_or(ptr::null_mut());
476
477    let subargs = arg.subargs.as_ref().map_or(ptr::null_mut(), |v| {
478        Box::into_raw(
479            v.iter()
480                .map(convert_command_arg_to_raw)
481                .chain(iter::once(unsafe { MaybeUninit::zeroed().assume_init() }))
482                .collect::<Vec<_>>()
483                .into_boxed_slice(),
484        )
485        .cast()
486    });
487
488    raw::RedisModuleCommandArg {
489        name,
490        type_: arg.type_,
491        key_spec_index: arg.key_spec_index.unwrap_or(u32::MAX) as c_int,
492        token,
493        summary,
494        since,
495        flags: arg.flags.bits() as c_int,
496        deprecated_since,
497        subargs,
498        display_text,
499    }
500}
501
502pub fn get_redis_command_args(
503    args: Vec<RedisModuleCommandArg>,
504) -> Option<Vec<raw::RedisModuleCommandArg>> {
505    if args.is_empty() {
506        return None;
507    }
508
509    let raw_args: Vec<raw::RedisModuleCommandArg> = args
510        .iter()
511        .map(convert_command_arg_to_raw)
512        .chain(iter::once(unsafe { MaybeUninit::zeroed().assume_init() }))
513        .collect();
514
515    Some(raw_args)
516}
517
518fn free_command_arg(arg: &raw::RedisModuleCommandArg) {
519    if !arg.name.is_null() {
520        drop(unsafe { CString::from_raw(arg.name as *mut c_char) });
521    }
522    if !arg.token.is_null() {
523        drop(unsafe { CString::from_raw(arg.token as *mut c_char) });
524    }
525    if !arg.summary.is_null() {
526        drop(unsafe { CString::from_raw(arg.summary as *mut c_char) });
527    }
528    if !arg.since.is_null() {
529        drop(unsafe { CString::from_raw(arg.since as *mut c_char) });
530    }
531    if !arg.deprecated_since.is_null() {
532        drop(unsafe { CString::from_raw(arg.deprecated_since as *mut c_char) });
533    }
534    if !arg.display_text.is_null() {
535        drop(unsafe { CString::from_raw(arg.display_text as *mut c_char) });
536    }
537
538    if !arg.subargs.is_null() {
539        let mut i = 0;
540        loop {
541            let subarg_ptr = unsafe { arg.subargs.offset(i) };
542            if unsafe { (*subarg_ptr).name.is_null() } {
543                break;
544            }
545            free_command_arg(unsafe { &*subarg_ptr });
546            i += 1;
547        }
548        let len = i as usize + 1;
549        drop(unsafe { Vec::from_raw_parts(arg.subargs, len, len) });
550    }
551}
552
553api! {[
554        RedisModule_CreateCommand,
555        RedisModule_GetCommand,
556        RedisModule_SetCommandInfo,
557        RedisModule_SetCommandACLCategories,
558    ],
559    /// Register all the commands located on `COMMNADS_LIST`.
560    fn register_commands_internal(ctx: &Context) -> Result<(), RedisError> {
561        let is_enterprise = ctx.is_enterprise();
562        COMMANDS_LIST.iter().try_for_each(|command| {
563            let command_info = command()?;
564            let name: CString = CString::new(command_info.name.as_str()).unwrap();
565            let mut flags = command_info.flags.as_deref().unwrap_or("").to_owned();
566            if is_enterprise {
567                flags = format!("{flags} {}", command_info.enterprise_flags.as_deref().unwrap_or("")).trim().to_owned();
568            }
569            let flags = CString::new(flags).map_err(|e| RedisError::String(e.to_string()))?;
570
571            if unsafe {
572                RedisModule_CreateCommand(
573                    ctx.ctx,
574                    name.as_ptr(),
575                    Some(command_info.callback),
576                    flags.as_ptr(),
577                    0,
578                    0,
579                    0,
580                )
581            } == raw::Status::Err as i32
582            {
583                return Err(RedisError::String(format!(
584                    "Failed register command {}.",
585                    command_info.name
586                )));
587            }
588
589            // Register the extra data of the command
590            let command = unsafe { RedisModule_GetCommand(ctx.ctx, name.as_ptr()) };
591
592            if command.is_null() {
593                return Err(RedisError::String(format!(
594                    "Failed finding command {} after registration.",
595                    command_info.name
596                )));
597            }
598
599            if let Some(acl_categories) = command_info.acl_categories {
600                let acl_categories = CString::new(acl_categories.join(" ")).map_err(|e| RedisError::String(e.to_string()))?;
601                if unsafe { RedisModule_SetCommandACLCategories(command, acl_categories.as_ptr()) } == raw::Status::Err as i32 {
602                    return Err(RedisError::String(format!(
603                        "Failed setting ACL categories for command {}.",
604                        command_info.name
605                    )));
606                }
607            }
608
609            let summary = command_info
610                .summary
611                .as_ref()
612                .map(|v| Some(CString::new(v.as_str()).unwrap()))
613                .unwrap_or(None);
614            let complexity = command_info
615                .complexity
616                .as_ref()
617                .map(|v| Some(CString::new(v.as_str()).unwrap()))
618                .unwrap_or(None);
619            let since = command_info
620                .since
621                .as_ref()
622                .map(|v| Some(CString::new(v.as_str()).unwrap()))
623                .unwrap_or(None);
624            let tips = command_info
625                .tips
626                .as_ref()
627                .map(|v| Some(CString::new(v.as_str()).unwrap()))
628                .unwrap_or(None);
629
630            let key_specs = get_redis_key_spec(command_info.key_spec);
631
632            let args = get_redis_command_args(command_info.args);
633
634            let mut redis_command_info = raw::RedisModuleCommandInfo {
635                version: &COMMNAD_INFO_VERSION,
636                summary: summary.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null_mut()),
637                complexity: complexity.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null_mut()),
638                since: since.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null_mut()),
639                history: ptr::null_mut(), // currently we will not support history
640                tips: tips.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null_mut()),
641                arity: command_info.arity as c_int,
642                key_specs: key_specs.as_ptr() as *mut raw::RedisModuleCommandKeySpec,
643                args: args.as_ref().map(Vec::as_ptr).unwrap_or(ptr::null_mut()) as *mut raw::RedisModuleCommandArg,
644            };
645
646            if unsafe { RedisModule_SetCommandInfo(command, &mut redis_command_info as *mut raw::RedisModuleCommandInfo) } == raw::Status::Err as i32 {
647                return Err(RedisError::String(format!(
648                    "Failed setting info for command {}.",
649                    command_info.name
650                )));
651            }
652
653            // the only CString pointers which are not freed are those of the key_specs, lets free them here.
654            key_specs.into_iter().for_each(|v|{
655                if !v.notes.is_null() {
656                    drop(unsafe{CString::from_raw(v.notes as *mut c_char)});
657                }
658                if v.begin_search_type == raw::RedisModuleKeySpecBeginSearchType_REDISMODULE_KSPEC_BS_KEYWORD {
659                    let keyword = unsafe{v.bs.keyword.keyword};
660                    if !keyword.is_null() {
661                        drop(unsafe{CString::from_raw(v.bs.keyword.keyword as *mut c_char)});
662                    }
663                }
664            });
665
666            args.unwrap_or_default().iter().for_each(free_command_arg);
667
668            Ok(())
669        })
670    }
671}
672
673#[cfg(all(
674    any(
675        feature = "min-redis-compatibility-version-8-0",
676        feature = "min-redis-compatibility-version-7-4",
677        feature = "min-redis-compatibility-version-7-2",
678        feature = "min-redis-compatibility-version-7-0"
679    ),
680    not(any(
681        feature = "min-redis-compatibility-version-6-2",
682        feature = "min-redis-compatibility-version-6-0"
683    ))
684))]
685pub fn register_commands(ctx: &Context) -> Status {
686    register_commands_internal(ctx).map_or_else(
687        |e| {
688            ctx.log_warning(&e.to_string());
689            Status::Err
690        },
691        |_| Status::Ok,
692    )
693}
694
695#[cfg(all(
696    any(
697        feature = "min-redis-compatibility-version-6-2",
698        feature = "min-redis-compatibility-version-6-0"
699    ),
700    not(any(
701        feature = "min-redis-compatibility-version-8-0",
702        feature = "min-redis-compatibility-version-7-4",
703        feature = "min-redis-compatibility-version-7-2",
704        feature = "min-redis-compatibility-version-7-0"
705    ))
706))]
707pub fn register_commands(ctx: &Context) -> Status {
708    register_commands_internal(ctx).map_or_else(
709        |e| {
710            /* Make sure new command registration API is not been used. */
711            if COMMANDS_LIST.is_empty() {
712                return Status::Ok;
713            }
714            ctx.log_warning(&e.to_string());
715            Status::Err
716        },
717        |v| {
718            v.map_or_else(
719                |e| {
720                    ctx.log_warning(&e.to_string());
721                    Status::Err
722                },
723                |_| Status::Ok,
724            )
725        },
726    )
727}