valkey_module/
macros.rs

1#[macro_export]
2macro_rules! redis_command {
3    (
4        $ctx:expr,
5        $command_name:expr,
6        $command_handler:expr,
7        $command_flags:expr,
8        $firstkey:expr,
9        $lastkey:expr,
10        $keystep:expr
11        $(,
12            $command_acl_categories:expr
13        )?
14        ) => {{
15        let name = CString::new($command_name).unwrap();
16        let flags = CString::new($command_flags).unwrap();
17        /////////////////////
18        extern "C" fn __do_command(
19            ctx: *mut $crate::raw::RedisModuleCtx,
20            argv: *mut *mut $crate::raw::RedisModuleString,
21            argc: c_int,
22        ) -> c_int {
23            let context = $crate::Context::new(ctx);
24            let args = $crate::decode_args(ctx, argv, argc);
25            let response = $command_handler(&context, args);
26            context.reply(response.map(|v| v.into())) as c_int
27        }
28
29        if unsafe {
30            $crate::raw::RedisModule_CreateCommand.unwrap()(
31                $ctx,
32                name.as_ptr(),
33                Some(__do_command),
34                flags.as_ptr(),
35                $firstkey,
36                $lastkey,
37                $keystep,
38            )
39        } == $crate::raw::Status::Err as c_int
40        {
41            return $crate::raw::Status::Err as c_int;
42        }
43
44        $(
45            let context = $crate::Context::new($ctx);
46            let acl_categories_to_add = CString::new($command_acl_categories).unwrap();
47            #[cfg(feature = "min-valkey-compatibility-version-8-0")]
48            context.set_acl_category(name.as_ptr(), acl_categories_to_add.as_ptr());
49        )?
50    }};
51}
52
53#[macro_export]
54macro_rules! redis_event_handler {
55    (
56        $ctx: expr,
57        $event_type: expr,
58        $event_handler: expr
59    ) => {{
60        extern "C" fn __handle_event(
61            ctx: *mut $crate::raw::RedisModuleCtx,
62            event_type: c_int,
63            event: *const c_char,
64            key: *mut $crate::raw::RedisModuleString,
65        ) -> c_int {
66            let context = $crate::Context::new(ctx);
67
68            let redis_key = $crate::ValkeyString::string_as_slice(key);
69            let event_str = unsafe { CStr::from_ptr(event) };
70            $event_handler(
71                &context,
72                $crate::NotifyEvent::from_bits_truncate(event_type),
73                event_str.to_str().unwrap(),
74                redis_key,
75            );
76
77            $crate::raw::Status::Ok as c_int
78        }
79
80        let all_available_notification_flags = $crate::raw::get_keyspace_notification_flags_all();
81        let available_wanted_notification_flags = $event_type.intersection(all_available_notification_flags);
82        if !all_available_notification_flags.contains($event_type) {
83            let not_supported = $event_type.difference(all_available_notification_flags);
84            $crate::Context::new($ctx).log_notice(&format!(
85                "These event notification flags set aren't supported: {not_supported:?}. These flags will be used: {available_wanted_notification_flags:?}"
86            ));
87        }
88
89        if !available_wanted_notification_flags.is_empty() && unsafe {
90            $crate::raw::RedisModule_SubscribeToKeyspaceEvents.unwrap()(
91                $ctx,
92                available_wanted_notification_flags.bits(),
93                Some(__handle_event),
94            )
95        } == $crate::raw::Status::Err as c_int
96        {
97            return $crate::raw::Status::Err as c_int;
98        }
99    }};
100}
101
102/// Defines a Valkey module.
103///
104/// It registers the defined module, sets it up and initialises properly,
105/// registers all the commands and types.
106#[macro_export]
107macro_rules! valkey_module {
108    (
109        name: $module_name:expr,
110        version: $module_version:expr,
111        /// Global allocator for the valkey module defined.
112        /// In most of the cases, the Valkey allocator ([crate::alloc::ValkeyAlloc])
113        /// should be used.
114        allocator: ($allocator_type:ty, $allocator_init:expr),
115        data_types: [
116            $($data_type:ident),* $(,)*
117        ],
118        $(init: $init_func:ident,)* $(,)*
119        $(deinit: $deinit_func:ident,)* $(,)*
120        $(info: $info_func:ident,)?
121        $(acl_categories: [
122            $($acl_category:expr),* $(,)*
123        ])?
124        commands: [
125            $([
126                $name:expr,
127                $command:expr,
128                $flags:expr,
129                $firstkey:expr,
130                $lastkey:expr,
131                $keystep:expr
132                $(,
133                    $command_acl_categories:expr
134                )?
135            ]),* $(,)?
136        ] $(,)*
137        $(event_handlers: [
138            $([
139                $(@$event_type:ident) +:
140                $event_handler:expr
141            ]),* $(,)*
142        ] $(,)* )?
143        $(configurations: [
144            $(i64:[$([
145                $i64_configuration_name:expr,
146                $i64_configuration_val:expr,
147                $i64_default:expr,
148                $i64_min:expr,
149                $i64_max:expr,
150                $i64_flags_options:expr,
151                $i64_on_changed:expr $(, $i64_on_set:expr)?
152            ]),* $(,)*],)?
153            $(string:[$([
154                $string_configuration_name:expr,
155                $string_configuration_val:expr,
156                $string_default:expr,
157                $string_flags_options:expr,
158                $string_on_changed:expr $(, $string_on_set:expr)?
159            ]),* $(,)*],)?
160            $(bool:[$([
161                $bool_configuration_name:expr,
162                $bool_configuration_val:expr,
163                $bool_default:expr,
164                $bool_flags_options:expr,
165                $bool_on_changed:expr $(, $bool_on_set:expr)?
166            ]),* $(,)*],)?
167            $(enum:[$([
168                $enum_configuration_name:expr,
169                $enum_configuration_val:expr,
170                $enum_default:expr,
171                $enum_flags_options:expr,
172                $enum_on_changed:expr $(, $enum_on_set:expr)?
173            ]),* $(,)*],)?
174            $(module_args_as_configuration:$use_module_args:expr,)?
175            $(module_config_get:$module_config_get_command:expr,)?
176            $(module_config_set:$module_config_set_command:expr,)?
177        ])?
178    ) => {
179        /// Valkey module allocator.
180        #[global_allocator]
181        static REDIS_MODULE_ALLOCATOR: $allocator_type = $allocator_init;
182
183        // The old-style info command handler, if specified.
184        $(
185            #[valkey_module_macros::info_command_handler]
186            #[inline]
187            fn module_info(ctx: &InfoContext, for_crash_report: bool) -> ValkeyResult<()> {
188                $info_func(ctx, for_crash_report);
189
190                Ok(())
191            }
192        )?
193
194        extern "C" fn __info_func(
195            ctx: *mut $crate::raw::RedisModuleInfoCtx,
196            for_crash_report: i32,
197        ) {
198            $crate::basic_info_command_handler(&$crate::InfoContext::new(ctx), for_crash_report == 1);
199        }
200
201        #[no_mangle]
202        #[allow(non_snake_case)]
203        pub unsafe extern "C" fn RedisModule_OnLoad(
204            ctx: *mut $crate::raw::RedisModuleCtx,
205            argv: *mut *mut $crate::raw::RedisModuleString,
206            argc: std::os::raw::c_int,
207        ) -> std::os::raw::c_int {
208            use std::os::raw::{c_int, c_char};
209            use std::ffi::{CString, CStr};
210
211            use $crate::raw;
212            use $crate::ValkeyString;
213            use $crate::server_events::register_server_events;
214            use $crate::configuration::register_i64_configuration;
215            use $crate::configuration::register_string_configuration;
216            use $crate::configuration::register_bool_configuration;
217            use $crate::configuration::register_enum_configuration;
218            use $crate::configuration::module_config_get;
219            use $crate::configuration::module_config_set;
220            use $crate::configuration::get_i64_default_config_value;
221            use $crate::configuration::get_string_default_config_value;
222            use $crate::configuration::get_bool_default_config_value;
223            use $crate::configuration::get_enum_default_config_value;
224
225            // We use a statically sized buffer to avoid allocating.
226            // This is needed since we use a custom allocator that relies on the Valkey allocator,
227            // which isn't yet ready at this point.
228            let mut name_buffer = [0; 64];
229            unsafe {
230                std::ptr::copy(
231                    $module_name.as_ptr(),
232                    name_buffer.as_mut_ptr(),
233                    $module_name.len(),
234                );
235            }
236
237            let module_version = $module_version as c_int;
238
239            // This block of code means that when Modules are compiled without the "use-redismodule-api" feature flag,
240            // we expect that ValkeyModule_Init should succeed. We do not YET utilize the ValkeyModule_Init invocation
241            // because the valkeymodule-rs still references RedisModule_* APIs for calls to the server.
242            if !raw::use_redis_module_api() {
243                let status = unsafe {
244                    raw::Export_ValkeyModule_Init(
245                        ctx as *mut raw::ValkeyModuleCtx,
246                        name_buffer.as_ptr().cast::<c_char>(),
247                        module_version,
248                        raw::VALKEYMODULE_APIVER_1 as c_int,
249                    )
250                };
251                if status == raw::Status::Err as c_int {
252                    return raw::Status::Err as c_int;
253                }
254            }
255
256            // For now, we need to initialize through RM_Init because several occurances are still using RedisModule_* APIs.
257            // Once we change every single Module API to be ValkeyModule_* (when the feature flag is not provided), we can
258            // update this block (invocation of RM_Init) to only be executed when the "use-redismodule-api" is provided.
259            let status = unsafe {
260                raw::Export_RedisModule_Init(
261                    ctx,
262                    name_buffer.as_ptr().cast::<c_char>(),
263                    module_version,
264                    raw::REDISMODULE_APIVER_1 as c_int,
265                )
266            };
267            if status == raw::Status::Err as c_int {
268                return raw::Status::Err as c_int;
269            }
270
271            let context = $crate::Context::new(ctx);
272            unsafe {
273                let _ = $crate::MODULE_CONTEXT.set_context(&context);
274            }
275            let args = $crate::decode_args(ctx, argv, argc);
276
277            $(
278                if (&$data_type).create_data_type(ctx).is_err() {
279                    return raw::Status::Err as c_int;
280                }
281            )*
282
283            $(
284                $(
285                    #[cfg(feature = "min-valkey-compatibility-version-8-0")]
286                    context.add_acl_category($acl_category);
287                )*
288            )?
289
290            $(
291                $crate::redis_command!(ctx, $name, $command, $flags, $firstkey, $lastkey, $keystep $(, $command_acl_categories)?);
292            )*
293
294            if $crate::commands::register_commands(&context) == raw::Status::Err {
295                return raw::Status::Err as c_int;
296            }
297
298            $(
299                $(
300                    $crate::redis_event_handler!(ctx, $(raw::NotifyEvent::$event_type |)+ raw::NotifyEvent::empty(), $event_handler);
301                )*
302            )?
303
304            $(
305                $(
306                    $(
307                        let default = if $use_module_args {
308                            match get_i64_default_config_value(&args, $i64_configuration_name, $i64_default) {
309                                Ok(v) => v,
310                                Err(e) => {
311                                    context.log_warning(&format!("{e}"));
312                                    return raw::Status::Err as c_int;
313                                }
314                            }
315                        } else {
316                            $i64_default
317                        };
318                        let mut use_fallback = true;
319                        $(
320                            use_fallback = false;
321                            register_i64_configuration(&context, $i64_configuration_name, $i64_configuration_val, default, $i64_min, $i64_max, $i64_flags_options, $i64_on_changed, $i64_on_set);
322                        )?
323                        if (use_fallback) {
324                            register_i64_configuration(&context, $i64_configuration_name, $i64_configuration_val, default, $i64_min, $i64_max, $i64_flags_options, $i64_on_changed, None);
325                        }
326                    )*
327                )?
328                $(
329                    $(
330                        let default = if $use_module_args {
331                            match get_string_default_config_value(&args, $string_configuration_name, $string_default) {
332                                Ok(v) => v,
333                                Err(e) => {
334                                    context.log_warning(&format!("{e}"));
335                                    return raw::Status::Err as c_int;
336                                }
337                            }
338                        } else {
339                            $string_default
340                        };
341                        let mut use_fallback = true;
342                        $(
343                            use_fallback = false;
344                            register_string_configuration(&context, $string_configuration_name, $string_configuration_val, default, $string_flags_options, $string_on_changed, $string_on_set);
345                        )?
346                        if (use_fallback) {
347                            register_string_configuration(&context, $string_configuration_name, $string_configuration_val, default, $string_flags_options, $string_on_changed, None);
348                        }
349                    )*
350                )?
351                $(
352                    $(
353                        let default = if $use_module_args {
354                            match get_bool_default_config_value(&args, $bool_configuration_name, $bool_default) {
355                                Ok(v) => v,
356                                Err(e) => {
357                                    context.log_warning(&format!("{e}"));
358                                    return raw::Status::Err as c_int;
359                                }
360                            }
361                        } else {
362                            $bool_default
363                        };
364                        let mut use_fallback = true;
365                        $(
366                            use_fallback = false;
367                            register_bool_configuration(&context, $bool_configuration_name, $bool_configuration_val, default, $bool_flags_options, $bool_on_changed, $bool_on_set);
368                        )?
369                        if (use_fallback) {
370                            register_bool_configuration(&context, $bool_configuration_name, $bool_configuration_val, default, $bool_flags_options, $bool_on_changed, None);
371                        }
372                    )*
373                )?
374                $(
375                    $(
376                        let default = if $use_module_args {
377                            match get_enum_default_config_value(&args, $enum_configuration_name, $enum_default) {
378                                Ok(v) => v,
379                                Err(e) => {
380                                    context.log_warning(&format!("{e}"));
381                                    return raw::Status::Err as c_int;
382                                }
383                            }
384                        } else {
385                            $enum_default
386                        };
387                        let mut use_fallback = true;
388                        $(
389                            use_fallback = false;
390                            register_enum_configuration(&context, $enum_configuration_name, $enum_configuration_val, default.clone(), $enum_flags_options, $enum_on_changed, $enum_on_set);
391                        )?
392                        if (use_fallback) {
393                            register_enum_configuration(&context, $enum_configuration_name, $enum_configuration_val, default.clone(), $enum_flags_options, $enum_on_changed, None);
394                        }
395                    )*
396                )?
397                raw::RedisModule_LoadConfigs.unwrap()(ctx);
398
399                $(
400                    $crate::redis_command!(ctx, $module_config_get_command, |ctx, args: Vec<RedisString>| {
401                        module_config_get(ctx, args, $module_name)
402                    }, "", 0, 0, 0);
403                )?
404
405                $(
406                    $crate::redis_command!(ctx, $module_config_set_command, |ctx, args: Vec<RedisString>| {
407                        module_config_set(ctx, args, $module_name)
408                    }, "", 0, 0, 0);
409                )?
410            )?
411
412            raw::register_info_function(ctx, Some(__info_func));
413
414            if let Err(e) = register_server_events(&context) {
415                context.log_warning(&format!("{e}"));
416                return raw::Status::Err as c_int;
417            }
418
419            $(
420                if $init_func(&context, &args) == $crate::Status::Err {
421                    return $crate::Status::Err as c_int;
422                }
423            )*
424
425            raw::Status::Ok as c_int
426        }
427
428        #[no_mangle]
429        #[allow(non_snake_case)]
430        pub extern "C" fn RedisModule_OnUnload(
431            ctx: *mut $crate::raw::RedisModuleCtx
432        ) -> std::os::raw::c_int {
433            use std::os::raw::c_int;
434
435            let context = $crate::Context::new(ctx);
436            $(
437                if $deinit_func(&context) == $crate::Status::Err {
438                    return $crate::Status::Err as c_int;
439                }
440            )*
441
442            $crate::raw::Status::Ok as c_int
443        }
444    }
445}