extern crate redis_module; use redis_module::native_types::RedisType; use redis_module::raw::RedisModuleTypeMethods; use redis_module::InfoContext; #[cfg(not(feature = "as-library"))] use redis_module::Status; #[cfg(not(feature = "as-library"))] use redis_module::{Context, RedisResult}; #[cfg(not(feature = "as-library"))] use crate::c_api::{ get_llapi_ctx, json_api_free_iter, json_api_get, json_api_get_at, json_api_get_boolean, json_api_get_double, json_api_get_int, json_api_get_json, json_api_get_len, json_api_get_string, json_api_get_type, json_api_is_json, json_api_len, json_api_next, json_api_open_key_internal, LLAPI_CTX, }; use crate::redisjson::Format; mod array_index; mod backward; pub mod c_api; pub mod commands; pub mod error; mod formatter; pub mod ivalue_manager; pub mod manager; mod nodevisitor; pub mod redisjson; pub const GIT_SHA: Option<&str> = std::option_env!("GIT_SHA"); pub const GIT_BRANCH: Option<&str> = std::option_env!("GIT_BRANCH"); pub const MODULE_NAME: &str = "ReJSON"; pub const MODULE_TYPE_NAME: &str = "ReJSON-RL"; pub const REDIS_JSON_TYPE_VERSION: i32 = 3; pub static REDIS_JSON_TYPE: RedisType = RedisType::new( MODULE_TYPE_NAME, REDIS_JSON_TYPE_VERSION, RedisModuleTypeMethods { version: redis_module::TYPE_METHOD_VERSION, rdb_load: Some(redisjson::type_methods::rdb_load), rdb_save: Some(redisjson::type_methods::rdb_save), aof_rewrite: None, // TODO add support free: Some(redisjson::type_methods::free), // Currently unused by Redis mem_usage: Some(redisjson::type_methods::mem_usage), digest: None, // Auxiliary data (v2) aux_load: None, aux_save: None, aux_save_triggers: 0, free_effort: None, unlink: None, copy: None, defrag: None, }, ); ///////////////////////////////////////////////////// #[derive(Copy, Clone)] pub enum ManagerType { SerdeValue, IValue, } pub static mut MANAGER: ManagerType = ManagerType::IValue; pub fn get_manager_type() -> ManagerType { unsafe { MANAGER } } #[macro_export] macro_rules! run_on_manager { ( $run:expr, $ctx:ident, $args: ident ) => { match $crate::get_manager_type() { $crate::ManagerType::IValue => $run( $crate::ivalue_manager::RedisIValueJsonKeyManager { phantom: PhantomData, }, $ctx, $args, ), $crate::ManagerType::SerdeValue => $run( $crate::manager::RedisJsonKeyManager { phantom: PhantomData, }, $ctx, $args, ), } }; } #[macro_export] macro_rules! redis_json_module_create {( data_types: [ $($data_type:ident),* $(,)* ], pre_command_function: $pre_command_function_expr:expr, get_manage: $get_manager_expr:expr, version: $version:expr, init: $init_func:expr, info: $info_func:ident, ) => { use redis_module::{redis_command, redis_module, RedisString}; use std::marker::PhantomData; use std::os::raw::{c_double, c_int, c_longlong}; use redis_module::{raw as rawmod, LogLevel}; use rawmod::ModuleOptions; use std::{ ffi::CStr, os::raw::{c_char, c_void}, }; use libc::size_t; use std::collections::HashMap; /// /// JSON.DEL [path] /// fn json_del(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_del(mngr, ctx, args), None => run_on_manager!(commands::command_json_del, ctx, args), } } /// /// JSON.GET /// [INDENT indentation-string] /// [NEWLINE line-break-string] /// [SPACE space-string] /// [path ...] /// /// TODO add support for multi path fn json_get(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_get(mngr, ctx, args), None => run_on_manager!(commands::command_json_get, ctx, args) } } /// /// JSON.SET [NX | XX | FORMAT ] /// fn json_set(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_set(mngr, ctx, args), None => run_on_manager!(commands::command_json_set, ctx, args) } } /// /// JSON.MGET [key ...] /// fn json_mget(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_mget(mngr, ctx, args), None => run_on_manager!(commands::command_json_mget, ctx, args) } } /// /// JSON.STRLEN [path] /// fn json_str_len(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_str_len(mngr, ctx, args), None => run_on_manager!(commands::command_json_str_len, ctx, args) } } /// /// JSON.TYPE [path] /// fn json_type(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_type(mngr, ctx, args), None => run_on_manager!(commands::command_json_type, ctx, args) } } /// /// JSON.NUMINCRBY /// fn json_num_incrby(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_num_incrby(mngr, ctx, args), None => run_on_manager!(commands::command_json_num_incrby, ctx, args) } } /// /// JSON.NUMMULTBY /// fn json_num_multby(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_num_multby(mngr, ctx, args), None => run_on_manager!(commands::command_json_num_multby, ctx, args) } } /// /// JSON.NUMPOWBY /// fn json_num_powby(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_num_powby(mngr, ctx, args), None => run_on_manager!(commands::command_json_num_powby, ctx, args) } } // /// JSON.TOGGLE fn json_bool_toggle(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_bool_toggle(mngr, ctx, args), None => run_on_manager!(commands::command_json_bool_toggle, ctx, args) } } /// /// JSON.STRAPPEND [path] /// fn json_str_append(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_str_append(mngr, ctx, args), None => run_on_manager!(commands::command_json_str_append, ctx, args) } } /// /// JSON.ARRAPPEND [json ...] /// fn json_arr_append(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_arr_append(mngr, ctx, args), None => run_on_manager!(commands::command_json_arr_append, ctx, args) } } /// /// JSON.ARRINDEX [start [stop]] /// /// scalar - number, string, Boolean (true or false), or null /// fn json_arr_index(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_arr_index(mngr, ctx, args), None => run_on_manager!(commands::command_json_arr_index, ctx, args) } } /// /// JSON.ARRINSERT [json ...] /// fn json_arr_insert(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_arr_insert(mngr, ctx, args), None => run_on_manager!(commands::command_json_arr_insert, ctx, args) } } /// /// JSON.ARRLEN [path] /// fn json_arr_len(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_arr_len(mngr, ctx, args), None => run_on_manager!(commands::command_json_arr_len, ctx, args) } } /// /// JSON.ARRPOP [path [index]] /// fn json_arr_pop(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_arr_pop(mngr, ctx, args), None => run_on_manager!(commands::command_json_arr_pop, ctx, args) } } /// /// JSON.ARRTRIM /// fn json_arr_trim(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_arr_trim(mngr, ctx, args), None => run_on_manager!(commands::command_json_arr_trim, ctx, args) } } /// /// JSON.OBJKEYS [path] /// fn json_obj_keys(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_obj_keys(mngr, ctx, args), None => run_on_manager!(commands::command_json_obj_keys, ctx, args) } } /// /// JSON.OBJLEN [path] /// fn json_obj_len(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_obj_len(mngr, ctx, args), None => run_on_manager!(commands::command_json_obj_len, ctx, args) } } /// /// JSON.CLEAR [path ...] /// fn json_clear(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_clear(mngr, ctx, args), None => run_on_manager!(commands::command_json_clear, ctx, args) } } /// /// JSON.DEBUG /// /// subcommands: /// MEMORY [path] /// HELP /// fn json_debug(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_debug(mngr, ctx, args), None => run_on_manager!(commands::command_json_debug, ctx, args) } } /// /// JSON.RESP [path] /// fn json_resp(ctx: &Context, args: Vec) -> RedisResult { $pre_command_function_expr(ctx, &args); let m = $get_manager_expr; match m { Some(mngr) => commands::command_json_resp(mngr, ctx, args), None => run_on_manager!(commands::command_json_resp, ctx, args) } } redis_json_module_export_shared_api! { get_manage:$get_manager_expr, pre_command_function: $pre_command_function_expr, } fn initialize(ctx: &Context, args: &Vec) -> Status { ctx.log_notice(&format!("version: {} git sha: {} branch: {}", $version, match GIT_SHA { Some(val) => val, _ => "unknown"}, match GIT_BRANCH { Some(val) => val, _ => "unknown"}, )); export_shared_api(ctx); ctx.set_module_options(ModuleOptions::HANDLE_IO_ERRORS); ctx.log_notice("Enabled diskless replication"); let valid_version = unsafe{ rawmod::RedisModule_GetServerVersion.map_or(false, |get_version|0x060200 <= get_version()) }; if valid_version { $init_func(ctx, args) } else { ctx.log_warning("RedisJson is only supported from redis-server version 6.2.0"); Status::Err } } fn json_init_config(ctx: &Context, args: &Vec) -> Status{ if args.len() % 2 != 0 { ctx.log(LogLevel::Warning, "RedisJson arguments must be key:value pairs"); return Status::Err; } let mut args_map = HashMap::::new(); for i in (0..args.len()).step_by(2) { args_map.insert(args[i].to_string_lossy(), args[i + 1].to_string_lossy()); } if let Some(backend) = args_map.get("JSON_BACKEND") { if backend == "SERDE_JSON" { unsafe {$crate::MANAGER = $crate::ManagerType::SerdeValue}; } else if backend == "IJSON" { unsafe {$crate::MANAGER = $crate::ManagerType::IValue}; } else { ctx.log(LogLevel::Warning, "Unsupported json backend was given"); return Status::Err; } } Status::Ok } redis_module! { name: crate::MODULE_NAME, version: $version, data_types: [$($data_type,)*], init: json_init_config, init: initialize, info: $info_func, commands: [ ["json.del", json_del, "write", 1,1,1], ["json.get", json_get, "readonly", 1,1,1], ["json.mget", json_mget, "readonly", 1,1,1], ["json.set", json_set, "write deny-oom", 1,1,1], ["json.type", json_type, "readonly", 1,1,1], ["json.numincrby", json_num_incrby, "write", 1,1,1], ["json.toggle", json_bool_toggle, "write deny-oom", 1,1,1], ["json.nummultby", json_num_multby, "write", 1,1,1], ["json.numpowby", json_num_powby, "write", 1,1,1], ["json.strappend", json_str_append, "write deny-oom", 1,1,1], ["json.strlen", json_str_len, "readonly", 1,1,1], ["json.arrappend", json_arr_append, "write deny-oom", 1,1,1], ["json.arrindex", json_arr_index, "readonly", 1,1,1], ["json.arrinsert", json_arr_insert, "write deny-oom", 1,1,1], ["json.arrlen", json_arr_len, "readonly", 1,1,1], ["json.arrpop", json_arr_pop, "write", 1,1,1], ["json.arrtrim", json_arr_trim, "write", 1,1,1], ["json.objkeys", json_obj_keys, "readonly", 1,1,1], ["json.objlen", json_obj_len, "readonly", 1,1,1], ["json.clear", json_clear, "write", 1,1,1], ["json.debug", json_debug, "readonly", 2,2,1], ["json.forget", json_del, "write", 1,1,1], ["json.resp", json_resp, "readonly", 1,1,1], ], } } } #[cfg(not(feature = "as-library"))] const fn pre_command(_ctx: &Context, _args: &Vec) {} #[cfg(not(feature = "as-library"))] const fn dummy_init(_ctx: &Context, _args: &Vec) -> Status { Status::Ok } #[cfg(not(feature = "as-library"))] const fn dummy_info(_ctx: &InfoContext, _for_crash_report: bool) {} #[cfg(not(feature = "as-library"))] redis_json_module_create! { data_types: [REDIS_JSON_TYPE], pre_command_function: pre_command, get_manage: { match get_manager_type() { ManagerType::IValue => Some(ivalue_manager::RedisIValueJsonKeyManager{phantom:PhantomData}), _ => None, } }, version: 99_99_99, init: dummy_init, info: dummy_info, }