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 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#[macro_export]
105macro_rules! define_extern_c_filter_func {
106 ($filter_func_name:ident, $filter_func:expr) => {
107 extern "C" fn $filter_func_name(ctx: *mut valkey_module::RedisModuleCommandFilterCtx) {
108 $filter_func(ctx);
109 }
110 };
111}
112
113#[macro_export]
118macro_rules! valkey_module {
119 (
120 name: $module_name:expr,
121 version: $module_version:expr,
122 allocator: ($allocator_type:ty, $allocator_init:expr),
126 data_types: [
127 $($data_type:ident),* $(,)*
128 ],
129 $(preload: $preload_func:ident,)* $(,)*
130 $(init: $init_func:ident,)* $(,)*
131 $(deinit: $deinit_func:ident,)* $(,)*
132 $(info: $info_func:ident,)?
133 $(acl_categories: [
134 $($acl_category:expr),* $(,)*
135 ])?
136 commands: [
137 $([
138 $name:expr,
139 $command:expr,
140 $flags:expr,
141 $firstkey:expr,
142 $lastkey:expr,
143 $keystep:expr
144 $(,
145 $command_acl_categories:expr
146 )?
147 ]),* $(,)?
148 ] $(,)*
149 $(
150 filters: [
151 $([
152 $filter_func:ident,
153 $filter_flags:expr
154 ]),* $(,)?
155 ] $(,)*
156 )?
157 $(event_handlers: [
158 $([
159 $(@$event_type:ident) +:
160 $event_handler:expr
161 ]),* $(,)*
162 ] $(,)* )?
163 $(configurations: [
164 $(i64:[$([
165 $i64_configuration_name:expr,
166 $i64_configuration_val:expr,
167 $i64_default:expr,
168 $i64_min:expr,
169 $i64_max:expr,
170 $i64_flags_options:expr,
171 $i64_on_changed:expr $(, $i64_on_set:expr)?
172 ]),* $(,)*],)?
173 $(string:[$([
174 $string_configuration_name:expr,
175 $string_configuration_val:expr,
176 $string_default:expr,
177 $string_flags_options:expr,
178 $string_on_changed:expr $(, $string_on_set:expr)?
179 ]),* $(,)*],)?
180 $(bool:[$([
181 $bool_configuration_name:expr,
182 $bool_configuration_val:expr,
183 $bool_default:expr,
184 $bool_flags_options:expr,
185 $bool_on_changed:expr $(, $bool_on_set:expr)?
186 ]),* $(,)*],)?
187 $(enum:[$([
188 $enum_configuration_name:expr,
189 $enum_configuration_val:expr,
190 $enum_default:expr,
191 $enum_flags_options:expr,
192 $enum_on_changed:expr $(, $enum_on_set:expr)?
193 ]),* $(,)*],)?
194 $(module_args_as_configuration:$use_module_args:expr,)?
195 $(module_config_get:$module_config_get_command:expr,)?
196 $(module_config_set:$module_config_set_command:expr,)?
197 ])?
198 ) => {
199 #[global_allocator]
201 static REDIS_MODULE_ALLOCATOR: $allocator_type = $allocator_init;
202
203 use std::sync::OnceLock;
204 static CMD_FILTERS: OnceLock<Vec<valkey_module::CommandFilter>> = OnceLock::new();
205
206 $(
208 #[valkey_module_macros::info_command_handler]
209 #[inline]
210 fn module_info(ctx: &InfoContext, for_crash_report: bool) -> ValkeyResult<()> {
211 $info_func(ctx, for_crash_report);
212
213 Ok(())
214 }
215 )?
216
217 extern "C" fn __info_func(
218 ctx: *mut $crate::raw::RedisModuleInfoCtx,
219 for_crash_report: i32,
220 ) {
221 $crate::basic_info_command_handler(&$crate::InfoContext::new(ctx), for_crash_report == 1);
222 }
223
224 #[no_mangle]
225 #[allow(non_snake_case)]
226 pub unsafe extern "C" fn RedisModule_OnLoad(
227 ctx: *mut $crate::raw::RedisModuleCtx,
228 argv: *mut *mut $crate::raw::RedisModuleString,
229 argc: std::os::raw::c_int,
230 ) -> std::os::raw::c_int {
231 use std::os::raw::{c_int, c_char};
232 use std::ffi::{CString, CStr};
233
234 use $crate::raw;
235 use $crate::ValkeyString;
236 use $crate::server_events::register_server_events;
237 use $crate::configuration::register_i64_configuration;
238 use $crate::configuration::register_string_configuration;
239 use $crate::configuration::register_bool_configuration;
240 use $crate::configuration::register_enum_configuration;
241 use $crate::configuration::module_config_get;
242 use $crate::configuration::module_config_set;
243 use $crate::configuration::get_i64_default_config_value;
244 use $crate::configuration::get_string_default_config_value;
245 use $crate::configuration::get_bool_default_config_value;
246 use $crate::configuration::get_enum_default_config_value;
247
248 let mut name_buffer = [0; 64];
252 unsafe {
253 std::ptr::copy(
254 $module_name.as_ptr(),
255 name_buffer.as_mut_ptr(),
256 $module_name.len(),
257 );
258 }
259
260 let module_version = $module_version as c_int;
261
262 if !raw::use_redis_module_api() {
266 let status = unsafe {
267 raw::Export_ValkeyModule_Init(
268 ctx as *mut raw::ValkeyModuleCtx,
269 name_buffer.as_ptr().cast::<c_char>(),
270 module_version,
271 raw::VALKEYMODULE_APIVER_1 as c_int,
272 )
273 };
274 if status == raw::Status::Err as c_int {
275 return raw::Status::Err as c_int;
276 }
277 }
278
279 let status = unsafe {
283 raw::Export_RedisModule_Init(
284 ctx,
285 name_buffer.as_ptr().cast::<c_char>(),
286 module_version,
287 raw::REDISMODULE_APIVER_1 as c_int,
288 )
289 };
290 if status == raw::Status::Err as c_int {
291 return raw::Status::Err as c_int;
292 }
293
294 let context = $crate::Context::new(ctx);
295 unsafe {
296 let _ = $crate::MODULE_CONTEXT.set_context(&context);
297 }
298 let args = $crate::decode_args(ctx, argv, argc);
299
300 $(
304 if $preload_func(&context, &args) == $crate::Status::Err {
305 return $crate::Status::Err as c_int;
306 }
307 )*
308
309 $(
310 if (&$data_type).create_data_type(ctx).is_err() {
311 return raw::Status::Err as c_int;
312 }
313 )*
314
315 $(
316 $(
317 #[cfg(feature = "min-valkey-compatibility-version-8-0")]
318 context.add_acl_category($acl_category);
319 )*
320 )?
321
322 $(
323 $crate::redis_command!(ctx, $name, $command, $flags, $firstkey, $lastkey, $keystep $(, $command_acl_categories)?);
324 )*
325
326 if $crate::commands::register_commands(&context) == raw::Status::Err {
327 return raw::Status::Err as c_int;
328 }
329
330 $(
332 let mut cmd_filters_vec = Vec::new();
333 $(
334 paste::paste! {
335 $crate::define_extern_c_filter_func!([<__extern_c_ $filter_func>], $filter_func);
337 let cmd_filter = context.register_command_filter([<__extern_c_ $filter_func>], $filter_flags);
338 cmd_filters_vec.push(cmd_filter);
339 }
340 )*
341 CMD_FILTERS.get_or_init(|| cmd_filters_vec);
343 )?
344
345 $(
346 $(
347 $crate::redis_event_handler!(ctx, $(raw::NotifyEvent::$event_type |)+ raw::NotifyEvent::empty(), $event_handler);
348 )*
349 )?
350
351 $(
352 $(
353 $(
354 let default = if $use_module_args {
355 match get_i64_default_config_value(&args, $i64_configuration_name, $i64_default) {
356 Ok(v) => v,
357 Err(e) => {
358 context.log_warning(&format!("{e}"));
359 return raw::Status::Err as c_int;
360 }
361 }
362 } else {
363 $i64_default
364 };
365 let mut use_fallback = true;
366 $(
367 use_fallback = false;
368 register_i64_configuration(&context, $i64_configuration_name, $i64_configuration_val, default, $i64_min, $i64_max, $i64_flags_options, $i64_on_changed, $i64_on_set);
369 )?
370 if (use_fallback) {
371 register_i64_configuration(&context, $i64_configuration_name, $i64_configuration_val, default, $i64_min, $i64_max, $i64_flags_options, $i64_on_changed, None);
372 }
373 )*
374 )?
375 $(
376 $(
377 let default = if $use_module_args {
378 match get_string_default_config_value(&args, $string_configuration_name, $string_default) {
379 Ok(v) => v,
380 Err(e) => {
381 context.log_warning(&format!("{e}"));
382 return raw::Status::Err as c_int;
383 }
384 }
385 } else {
386 $string_default
387 };
388 let mut use_fallback = true;
389 $(
390 use_fallback = false;
391 register_string_configuration(&context, $string_configuration_name, $string_configuration_val, default, $string_flags_options, $string_on_changed, $string_on_set);
392 )?
393 if (use_fallback) {
394 register_string_configuration(&context, $string_configuration_name, $string_configuration_val, default, $string_flags_options, $string_on_changed, None);
395 }
396 )*
397 )?
398 $(
399 $(
400 let default = if $use_module_args {
401 match get_bool_default_config_value(&args, $bool_configuration_name, $bool_default) {
402 Ok(v) => v,
403 Err(e) => {
404 context.log_warning(&format!("{e}"));
405 return raw::Status::Err as c_int;
406 }
407 }
408 } else {
409 $bool_default
410 };
411 let mut use_fallback = true;
412 $(
413 use_fallback = false;
414 register_bool_configuration(&context, $bool_configuration_name, $bool_configuration_val, default, $bool_flags_options, $bool_on_changed, $bool_on_set);
415 )?
416 if (use_fallback) {
417 register_bool_configuration(&context, $bool_configuration_name, $bool_configuration_val, default, $bool_flags_options, $bool_on_changed, None);
418 }
419 )*
420 )?
421 $(
422 $(
423 let default = if $use_module_args {
424 match get_enum_default_config_value(&args, $enum_configuration_name, $enum_default) {
425 Ok(v) => v,
426 Err(e) => {
427 context.log_warning(&format!("{e}"));
428 return raw::Status::Err as c_int;
429 }
430 }
431 } else {
432 $enum_default
433 };
434 let mut use_fallback = true;
435 $(
436 use_fallback = false;
437 register_enum_configuration(&context, $enum_configuration_name, $enum_configuration_val, default.clone(), $enum_flags_options, $enum_on_changed, $enum_on_set);
438 )?
439 if (use_fallback) {
440 register_enum_configuration(&context, $enum_configuration_name, $enum_configuration_val, default.clone(), $enum_flags_options, $enum_on_changed, None);
441 }
442 )*
443 )?
444 raw::RedisModule_LoadConfigs.unwrap()(ctx);
445
446 $(
447 $crate::redis_command!(ctx, $module_config_get_command, |ctx, args: Vec<RedisString>| {
448 module_config_get(ctx, args, $module_name)
449 }, "", 0, 0, 0);
450 )?
451
452 $(
453 $crate::redis_command!(ctx, $module_config_set_command, |ctx, args: Vec<RedisString>| {
454 module_config_set(ctx, args, $module_name)
455 }, "", 0, 0, 0);
456 )?
457 )?
458
459 raw::register_info_function(ctx, Some(__info_func));
460
461 if let Err(e) = register_server_events(&context) {
462 context.log_warning(&format!("{e}"));
463 return raw::Status::Err as c_int;
464 }
465
466 $(
467 if $init_func(&context, &args) == $crate::Status::Err {
468 return $crate::Status::Err as c_int;
469 }
470 )*
471
472 raw::Status::Ok as c_int
473 }
474
475 #[no_mangle]
476 #[allow(non_snake_case)]
477 pub extern "C" fn RedisModule_OnUnload(
478 ctx: *mut $crate::raw::RedisModuleCtx
479 ) -> std::os::raw::c_int {
480 use std::os::raw::c_int;
481
482 let context = $crate::Context::new(ctx);
483 $(
484 if $deinit_func(&context) == $crate::Status::Err {
485 return $crate::Status::Err as c_int;
486 }
487 )*
488
489 let cmd_filters_vec = match CMD_FILTERS.get(){
491 Some(tmp) => tmp,
492 None => &vec![]
493 };
494 for filter in cmd_filters_vec {
495 context.unregister_command_filter(&filter);
496 }
497
498 $crate::raw::Status::Ok as c_int
499 }
500 }
501}