From 55e5ea97fd528b152f82c447962370d9c73e1c34 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Thu, 8 Aug 2024 16:30:11 +0300 Subject: [PATCH 01/33] placeholder --- redis_json/src/c_api.rs | 4 +- redis_json/src/commands.rs | 155 ++++++++++++++++------------------ redis_json/src/formatter.rs | 11 +-- redis_json/src/key_value.rs | 160 ++++++++++++++++-------------------- redis_json/src/redisjson.rs | 2 +- 5 files changed, 153 insertions(+), 179 deletions(-) diff --git a/redis_json/src/c_api.rs b/redis_json/src/c_api.rs index fea81f207..5d175fb05 100644 --- a/redis_json/src/c_api.rs +++ b/redis_json/src/c_api.rs @@ -145,7 +145,7 @@ pub fn json_api_get_json( str: *mut *mut rawmod::RedisModuleString, ) -> c_int { let json = unsafe { &*(json.cast::()) }; - let res = KeyValue::::serialize_object(json, &ReplyFormatOptions::default()); + let res = KeyValue::::serialize_object(json, ReplyFormatOptions::default()); create_rmstring(ctx, &res, str) } @@ -159,7 +159,7 @@ pub fn json_api_get_json_from_iter( if iter.pos >= iter.results.len() { Status::Err as c_int } else { - let res = KeyValue::::serialize_object(&iter.results, &ReplyFormatOptions::default()); + let res = KeyValue::::serialize_object(&iter.results, ReplyFormatOptions::default()); create_rmstring(ctx, &res, str); Status::Ok as c_int } diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index 430520182..405dca268 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -146,7 +146,7 @@ pub fn json_get(manager: M, ctx: &Context, args: Vec) - let key = manager.open_key_read(ctx, &key)?; let value = match key.get_value()? { - Some(doc) => KeyValue::new(doc).to_json(paths, &format_options)?, + Some(doc) => KeyValue::new(doc).to_json(paths, format_options)?, None => RedisValue::Null, }; @@ -391,16 +391,13 @@ fn find_paths bool>( doc: &T, mut f: F, ) -> Result>, RedisError> { - let query = match compile(path) { - Ok(q) => q, - Err(e) => return Err(RedisError::String(e.to_string())), - }; - let res = calc_once_with_paths(query, doc); - Ok(res + let query = compile(path).map_err(|e| RedisError::String(e.to_string()))?; + let res = calc_once_with_paths(query, doc) .into_iter() .filter(|e| f(e.res)) .map(|e| e.path_tracker.unwrap().to_string_path()) - .collect()) + .collect(); + Ok(res) } /// Returns tuples of Value and its concrete path which match the given `path` @@ -408,15 +405,12 @@ fn get_all_values_and_paths<'a, T: SelectValue>( path: &str, doc: &'a T, ) -> Result)>, RedisError> { - let query = match compile(path) { - Ok(q) => q, - Err(e) => return Err(RedisError::String(e.to_string())), - }; - let res = calc_once_with_paths(query, doc); - Ok(res + let query = compile(path).map_err(|e| RedisError::String(e.to_string()))?; + let res = calc_once_with_paths(query, doc) .into_iter() .map(|e| (e.res, e.path_tracker.unwrap().to_string_path())) - .collect()) + .collect(); + Ok(res) } /// Returns a Vec of paths with `None` for Values that do not match the filter @@ -450,10 +444,7 @@ where F: Fn(&T) -> bool, { let res = get_all_values_and_paths(path, doc)?; - match res.is_empty() { - false => Ok(filter_paths(res, f)), - _ => Ok(vec![]), - } + Ok(filter_paths(res, f)) } fn find_all_values<'a, T: SelectValue, F>( @@ -465,10 +456,7 @@ where F: Fn(&T) -> bool, { let res = get_all_values_and_paths(path, doc)?; - match res.is_empty() { - false => Ok(filter_values(res, f)), - _ => Ok(vec![]), - } + Ok(filter_values(res, f)) } fn to_json_value(values: Vec>, none_value: Value) -> Vec @@ -524,8 +512,7 @@ pub fn prepare_paths_for_updating(paths: &mut Vec>) { }); // Remove paths which are nested by others (on each sub-tree only top most ancestor should be deleted) // (TODO: Add a mode in which the jsonpath selector will already skip nested paths) - let mut string_paths = paths.iter().map(|v| v.join(",")).collect_vec(); - string_paths.sort(); + let string_paths = paths.iter().map(|v| v.join(",")).sorted().collect_vec(); paths.retain(|v| { let path = v.join(","); @@ -559,13 +546,9 @@ pub fn json_del(manager: M, ctx: &Context, args: Vec) - } else { let mut paths = find_paths(path.get_path(), doc, |_| true)?; prepare_paths_for_updating(&mut paths); - let mut changed = 0; - for p in paths { - if redis_key.delete_path(p)? { - changed += 1; - } - } - changed + paths + .into_iter() + .try_fold(0, |acc, p| redis_key.delete_path(p).and(Ok(acc + 1)))? }; if res > 0 { redis_key.notify_keyspace_event(ctx, "json.del")?; @@ -590,35 +573,37 @@ pub fn json_mget(manager: M, ctx: &Context, args: Vec) let path = Path::new(path.try_as_str()?); let keys = &args[1..args.len() - 1]; - let format_options = ReplyFormatOptions::new(is_resp3(ctx), ReplyFormat::STRING); - // Verify that at least one key exists if keys.is_empty() { return Err(RedisError::WrongArity); } - let results: Result, RedisError> = keys - .iter() + let results = keys + .into_iter() .map(|key| { manager .open_key_read(ctx, key) - .map_or(Ok(RedisValue::Null), |json_key| { - json_key.get_value().map_or(Ok(RedisValue::Null), |value| { - value.map_or(Ok(RedisValue::Null), |doc| { + .map_or(RedisValue::Null, |json_key| { + json_key + .get_value() + .ok() + .flatten() + .map_or(RedisValue::Null, |doc| { let key_value = KeyValue::new(doc); - let res = if !path.is_legacy() { - key_value.to_string_multi(path.get_path(), &format_options) + let format_options = + ReplyFormatOptions::new(is_resp3(ctx), ReplyFormat::STRING); + if !path.is_legacy() { + key_value.to_string_multi(path.get_path(), format_options) } else { - key_value.to_string_single(path.get_path(), &format_options) - }; - Ok(res.map_or(RedisValue::Null, |v| v.into())) + key_value.to_string_single(path.get_path(), format_options) + } + .map_or(RedisValue::Null, |v| v.into()) }) - }) }) }) - .collect(); + .collect_vec(); - Ok(results?.into()) + Ok(results.into()) }) } /// @@ -745,7 +730,7 @@ where // Convert to RESP2 format return as one JSON array let values = to_json_value::(results, Value::Null); - Ok(KeyValue::::serialize_object(&values, &ReplyFormatOptions::default()).into()) + Ok(KeyValue::::serialize_object(&values, ReplyFormatOptions::default()).into()) } } @@ -1351,49 +1336,51 @@ where /// pub fn json_arr_len(manager: M, ctx: &Context, args: Vec) -> RedisResult { let mut args = args.into_iter().skip(1); - let key = args.next_arg()?; + let key = manager.open_key_read(ctx, &args.next_arg()?)?; let path = Path::new(args.next_str().unwrap_or(default_path())); - let is_legacy = path.is_legacy(); - let key = manager.open_key_read(ctx, &key)?; + if path.is_legacy() { + json_arr_len_legacy::(key, path) + } else { + json_arr_len_impl::(key, path) + } +} + +fn json_arr_len_impl(key: M::ReadHolder, path: Path) -> RedisResult +where + M: Manager, +{ + let root = key.get_value()?.ok_or(RedisError::nonexistent_key())?; + let values = find_all_values(path.get_path(), root, |v| { + v.get_type() == SelectValueType::Array + })?; + let res = values + .into_iter() + .map(|v| v.map_or(RedisValue::Null, |v| v.len().unwrap().into())) + .collect_vec(); + Ok(res.into()) +} + +fn json_arr_len_legacy(key: M::ReadHolder, path: Path) -> RedisResult +where + M: Manager, +{ let root = match key.get_value()? { Some(k) => k, - None if is_legacy => { - return Ok(RedisValue::Null); - } None => { - return Err(RedisError::nonexistent_key()); + return Ok(RedisValue::Null); } }; let values = find_all_values(path.get_path(), root, |v| { v.get_type() == SelectValueType::Array })?; - if is_legacy && values.is_empty() { - return Err(RedisError::String( - err_msg_json_path_doesnt_exist_with_param(path.get_original()), - )); - } - let mut res = vec![]; - for v in values { - let cur_val: RedisValue = match v { - Some(v) => (v.len().unwrap() as i64).into(), - _ => { - if is_legacy { - return Err(RedisError::String( - err_msg_json_path_doesnt_exist_with_param_or( - path.get_original(), - "not an array", - ), - )); - } - RedisValue::Null - } - }; - if is_legacy { - return Ok(cur_val); - } - res.push(cur_val); - } - Ok(res.into()) + values.into_iter().next().flatten().map_or_else( + || { + Err(RedisError::String( + err_msg_json_path_doesnt_exist_with_param_or(path.get_original(), "not an array"), + )) + }, + |v| Ok(v.len().unwrap().into()), + ) } /// @@ -1462,7 +1449,7 @@ pub fn json_arr_pop(manager: M, ctx: &Context, args: Vec( ctx: &Context, path: &str, index: i64, - format_options: &ReplyFormatOptions, + format_options: ReplyFormatOptions, ) -> RedisResult where M: Manager, diff --git a/redis_json/src/formatter.rs b/redis_json/src/formatter.rs index f30b60e92..c34fb0f15 100644 --- a/redis_json/src/formatter.rs +++ b/redis_json/src/formatter.rs @@ -37,6 +37,7 @@ use std::io; pub use crate::redisjson::ReplyFormat; +#[derive(Clone, Copy)] pub struct ReplyFormatOptions<'a> { pub format: ReplyFormat, pub indent: Option<&'a str>, @@ -91,7 +92,7 @@ pub struct RedisJsonFormatter<'a> { } impl<'a> RedisJsonFormatter<'a> { - pub const fn new(format: &'a ReplyFormatOptions) -> Self { + pub const fn new(format: ReplyFormatOptions<'a>) -> Self { RedisJsonFormatter { current_indent: 0, has_value: false, @@ -220,7 +221,7 @@ mod tests { #[test] #[allow(clippy::cognitive_complexity)] fn test_default_formatter() { - let mut formatter = RedisJsonFormatter::new(&ReplyFormatOptions { + let mut formatter = RedisJsonFormatter::new(ReplyFormatOptions { format: ReplyFormat::STRING, indent: None, space: None, @@ -284,7 +285,7 @@ mod tests { #[test] #[allow(clippy::cognitive_complexity)] fn test_ident_formatter() { - let mut formatter = RedisJsonFormatter::new(&ReplyFormatOptions { + let mut formatter = RedisJsonFormatter::new(ReplyFormatOptions { format: ReplyFormat::STRING, indent: Some("_"), space: None, @@ -348,7 +349,7 @@ mod tests { #[test] #[allow(clippy::cognitive_complexity)] fn test_space_formatter() { - let mut formatter = RedisJsonFormatter::new(&ReplyFormatOptions { + let mut formatter = RedisJsonFormatter::new(ReplyFormatOptions { format: ReplyFormat::STRING, indent: None, space: Some("s"), @@ -412,7 +413,7 @@ mod tests { #[test] #[allow(clippy::cognitive_complexity)] fn test_new_line_formatter() { - let mut formatter = RedisJsonFormatter::new(&ReplyFormatOptions { + let mut formatter = RedisJsonFormatter::new(ReplyFormatOptions { format: ReplyFormat::STRING, indent: None, space: None, diff --git a/redis_json/src/key_value.rs b/redis_json/src/key_value.rs index dc4d49176..73b2a09bc 100644 --- a/redis_json/src/key_value.rs +++ b/redis_json/src/key_value.rs @@ -31,40 +31,35 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } pub fn get_first<'b>(&'a self, path: &'b str) -> Result<&'a V, Error> { - let results = self.get_values(path)?; - match results.first() { - Some(s) => Ok(s), - None => Err(err_msg_json_path_doesnt_exist_with_param(path) + self.get_values(path)?.first().copied().ok_or( + err_msg_json_path_doesnt_exist_with_param(path) .as_str() - .into()), - } + .into(), + ) } pub fn resp_serialize(&self, path: Path) -> RedisResult { - if path.is_legacy() { + let res = if path.is_legacy() { let v = self.get_first(path.get_path())?; - Ok(Self::resp_serialize_inner(v)) + Self::resp_serialize_inner(v) } else { - Ok(self - .get_values(path.get_path())? + self.get_values(path.get_path())? .into_iter() .map(|v| Self::resp_serialize_inner(v)) .collect_vec() - .into()) - } + .into() + }; + Ok(res) } fn resp_serialize_inner(v: &V) -> RedisValue { match v.get_type() { SelectValueType::Null => RedisValue::Null, - SelectValueType::Bool => { - let bool_val = v.get_bool(); - match bool_val { - true => RedisValue::SimpleString("true".to_string()), - false => RedisValue::SimpleString("false".to_string()), - } - } + SelectValueType::Bool => match v.get_bool() { + true => RedisValue::SimpleString("true".to_string()), + false => RedisValue::SimpleString("false".to_string()), + }, SelectValueType::Long => RedisValue::Integer(v.get_long()), @@ -73,21 +68,21 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { SelectValueType::String => RedisValue::BulkString(v.get_str()), SelectValueType::Array => { - let mut res: Vec = Vec::with_capacity(v.len().unwrap() + 1); - res.push(RedisValue::SimpleStringStatic("[")); - v.values() - .unwrap() - .for_each(|v| res.push(Self::resp_serialize_inner(v))); + let res = std::iter::once(RedisValue::SimpleStringStatic("[")) + .chain(v.values().unwrap().map(|v| Self::resp_serialize_inner(v))) + .collect(); RedisValue::Array(res) } SelectValueType::Object => { - let mut res: Vec = Vec::with_capacity(v.len().unwrap() + 1); - res.push(RedisValue::SimpleStringStatic("{")); - for (k, v) in v.items().unwrap() { - res.push(RedisValue::BulkString(k.to_string())); - res.push(Self::resp_serialize_inner(v)); - } + let res = std::iter::once(RedisValue::SimpleStringStatic("{")) + .chain(v.items().unwrap().flat_map(|(k, v)| { + [ + RedisValue::BulkString(k.to_string()), + Self::resp_serialize_inner(v), + ] + })) + .collect(); RedisValue::Array(res) } } @@ -99,7 +94,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { Ok(results) } - pub fn serialize_object(o: &O, format: &ReplyFormatOptions) -> String { + pub fn serialize_object(o: &O, format: ReplyFormatOptions) -> String { // When using the default formatting, we can use serde_json's default serializer if format.no_formatting() { serde_json::to_string(o).unwrap() @@ -114,39 +109,35 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { fn to_json_multi( &self, paths: Vec, - format: &ReplyFormatOptions, + format: ReplyFormatOptions, is_legacy: bool, ) -> Result { // TODO: Creating a temp doc here duplicates memory usage. This can be very memory inefficient. // A better way would be to create a doc of references to the original doc but no current support // in serde_json. I'm going for this implementation anyway because serde_json isn't supposed to be // memory efficient and we're using it anyway. See https://github.com/serde-rs/json/issues/635. - let mut missing_path = None; - let path_len = paths.len(); - let temp_doc = - paths - .into_iter() - .fold(HashMap::with_capacity(path_len), |mut acc, path: Path| { - // If we can't compile the path, we can't continue - if let Ok(query) = compile(path.get_path()) { - let results = calc_once(query, self.val); - - let value = if is_legacy { - (!results.is_empty()).then(|| Values::Single(results[0])) - } else { - Some(Values::Multi(results)) - }; - - if value.is_none() && missing_path.is_none() { - missing_path = Some(path.get_original().to_string()); + let temp_doc: HashMap<_, _> = paths + .into_iter() + .filter_map(|path| { + // If we can't compile the path, we can't continue + compile(path.get_path()).ok().map(|query| { + let results = calc_once(query, self.val); + + let value = if is_legacy { + if results.is_empty() { + return Err(err_msg_json_path_doesnt_exist_with_param( + path.get_original(), + )); } - acc.insert(path.get_original(), value); - } - acc - }); - if let Some(p) = missing_path { - return Err(err_msg_json_path_doesnt_exist_with_param(p.as_str()).into()); - } + Values::Single(results[0]) + } else { + Values::Multi(results) + }; + + Ok((path.get_original(), value)) + }) + }) + .try_collect()?; // If we're using RESP3, we need to convert the HashMap to a RedisValue::Map unless we're using the legacy format let res = if format.is_resp3_reply() { @@ -155,9 +146,8 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { .map(|(k, v)| { let key = RedisValueKey::String(k.to_string()); let value = match v { - Some(Values::Single(value)) => Self::value_to_resp3(value, format), - Some(Values::Multi(values)) => Self::values_to_resp3(&values, format), - None => RedisValue::Null, + Values::Single(value) => Self::value_to_resp3(value, format), + Values::Multi(values) => Self::values_to_resp3(values, format), }; (key, value) }) @@ -169,7 +159,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { Ok(res) } - fn to_resp3(&self, paths: Vec, format: &ReplyFormatOptions) -> Result { + fn to_resp3(&self, paths: Vec, format: ReplyFormatOptions) -> Result { let results = paths .into_iter() .map(|path: Path| self.to_resp3_path(&path, format)) @@ -177,38 +167,38 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { Ok(RedisValue::Array(results)) } - pub fn to_resp3_path(&self, path: &Path, format: &ReplyFormatOptions) -> RedisValue { + pub fn to_resp3_path(&self, path: &Path, format: ReplyFormatOptions) -> RedisValue { compile(path.get_path()).map_or(RedisValue::Array(vec![]), |q| { - Self::values_to_resp3(&calc_once(q, self.val), format) + Self::values_to_resp3(calc_once(q, self.val), format) }) } fn to_json_single( &self, path: &str, - format: &ReplyFormatOptions, + format: ReplyFormatOptions, is_legacy: bool, ) -> Result { let res = if is_legacy { self.to_string_single(path, format)?.into() } else if format.is_resp3_reply() { let values = self.get_values(path)?; - Self::values_to_resp3(&values, format) + Self::values_to_resp3(values, format) } else { self.to_string_multi(path, format)?.into() }; Ok(res) } - fn values_to_resp3(values: &[&V], format: &ReplyFormatOptions) -> RedisValue { + fn values_to_resp3(values: Vec<&V>, format: ReplyFormatOptions) -> RedisValue { values - .iter() + .into_iter() .map(|v| Self::value_to_resp3(v, format)) .collect_vec() .into() } - pub fn value_to_resp3(value: &V, format: &ReplyFormatOptions) -> RedisValue { + pub fn value_to_resp3(value: &V, format: ReplyFormatOptions) -> RedisValue { if format.format == ReplyFormat::EXPAND { match value.get_type() { SelectValueType::Null => RedisValue::Null, @@ -250,7 +240,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { pub fn to_json( &self, paths: Vec, - format: &ReplyFormatOptions, + format: ReplyFormatOptions, ) -> Result { let is_legacy = !paths.iter().any(|p| !p.is_legacy()); @@ -278,17 +268,15 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { match token_type { JsonPathToken::String => { - if query.size() == 1 { + let res = if query.size() == 1 { // Adding to the root - Ok(vec![UpdateInfo::AUI(AddUpdateInfo { + vec![UpdateInfo::AUI(AddUpdateInfo { path: Vec::new(), key: last, - })]) + })] } else { // Adding somewhere in existing object - let res = calc_once_paths(query, self.val); - - Ok(res + calc_once_paths(query, self.val) .into_iter() .map(|v| { UpdateInfo::AUI(AddUpdateInfo { @@ -296,8 +284,9 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { key: last.to_string(), }) }) - .collect()) - } + .collect() + }; + Ok(res) } JsonPathToken::Number => { // if we reach here with array path we are either out of range @@ -322,11 +311,12 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { if option != SetOptions::MergeExisting { prepare_paths_for_updating(&mut res); } + let res = res + .into_iter() + .map(|v| UpdateInfo::SUI(SetUpdateInfo { path: v })) + .collect_vec(); if !res.is_empty() { - return Ok(res - .into_iter() - .map(|v| UpdateInfo::SUI(SetUpdateInfo { path: v })) - .collect()); + return Ok(res); } } if option == SetOptions::AlreadyExists { @@ -339,17 +329,13 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { pub fn to_string_single( &self, path: &str, - format: &ReplyFormatOptions, + format: ReplyFormatOptions, ) -> Result { let result = self.get_first(path)?; Ok(Self::serialize_object(&result, format)) } - pub fn to_string_multi( - &self, - path: &str, - format: &ReplyFormatOptions, - ) -> Result { + pub fn to_string_multi(&self, path: &str, format: ReplyFormatOptions) -> Result { let results = self.get_values(path)?; Ok(Self::serialize_object(&results, format)) } diff --git a/redis_json/src/redisjson.rs b/redis_json/src/redisjson.rs index 1c1263c8b..e3ff3f8ae 100644 --- a/redis_json/src/redisjson.rs +++ b/redis_json/src/redisjson.rs @@ -76,7 +76,7 @@ impl FromStr for Format { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum ReplyFormat { STRING, STRINGS, From 8dd5acca8f21141d46861d37f342fdb0f2b88ce1 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Thu, 8 Aug 2024 18:39:25 +0300 Subject: [PATCH 02/33] work --- Cargo.lock | 1 + redis_json/Cargo.toml | 1 + redis_json/src/commands.rs | 265 +++++++++++++++--------------------- redis_json/src/redisjson.rs | 19 +++ 4 files changed, 131 insertions(+), 155 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11fc870dc..061445433 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -866,6 +866,7 @@ dependencies = [ "json_path", "libc", "linkme", + "once_cell", "redis-module", "redis-module-macros", "serde", diff --git a/redis_json/Cargo.toml b/redis_json/Cargo.toml index 4065b356c..336ca7b72 100644 --- a/redis_json/Cargo.toml +++ b/redis_json/Cargo.toml @@ -30,6 +30,7 @@ redis-module-macros = "^2.0.7" itertools = "0.13" json_path = {path="../json_path"} linkme = "0.3" +once_cell = "1.19" [features] as-library = [] diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index 405dca268..905352add 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -11,7 +11,7 @@ use crate::manager::{ err_msg_json_path_doesnt_exist_with_param, err_msg_json_path_doesnt_exist_with_param_or, Manager, ReadHolder, UpdateInfo, WriteHolder, }; -use crate::redisjson::{Format, Path, ReplyFormat, SetOptions}; +use crate::redisjson::{Format, Path, ReplyFormat, SetOptions, JSON_ROOT_PATH}; use json_path::select_value::{SelectValue, SelectValueType}; use redis_module::{Context, RedisValue}; use redis_module::{NextArg, RedisError, RedisResult, RedisString, REDIS_OK}; @@ -26,8 +26,6 @@ use itertools::FoldWhile::{Continue, Done}; use itertools::{EitherOrBoth, Itertools}; use serde::{Serialize, Serializer}; -const JSON_ROOT_PATH_LEGACY: &str = "."; -const JSON_ROOT_PATH: &str = "$"; const CMD_ARG_NOESCAPE: &str = "NOESCAPE"; const CMD_ARG_INDENT: &str = "INDENT"; const CMD_ARG_NEWLINE: &str = "NEWLINE"; @@ -84,11 +82,6 @@ fn is_resp3(ctx: &Context) -> bool { .contains(redis_module::ContextFlags::FLAGS_RESP3) } -/// Returns the deault path for the given RESP version -const fn default_path() -> &'static str { - JSON_ROOT_PATH_LEGACY -} - /// /// JSON.GET /// [INDENT indentation-string] @@ -141,7 +134,7 @@ pub fn json_get(manager: M, ctx: &Context, args: Vec) - // path is optional -> no path found we use legacy root "." if paths.is_empty() { - paths.push(Path::new(default_path())); + paths.push(Path::default()); } let key = manager.open_key_read(ctx, &key)?; @@ -188,7 +181,7 @@ pub fn json_set(manager: M, ctx: &Context, args: Vec) - match (current, set_option) { (Some(doc), op) => { - if path.get_path() == JSON_ROOT_PATH { + if path == *JSON_ROOT_PATH { if op != SetOptions::NotExists { redis_key.set_value(Vec::new(), val)?; redis_key.notify_keyspace_event(ctx, "json.set")?; @@ -210,7 +203,7 @@ pub fn json_set(manager: M, ctx: &Context, args: Vec) - } (None, SetOptions::AlreadyExists) => Ok(RedisValue::Null), _ => { - if path.get_path() == JSON_ROOT_PATH { + if path == *JSON_ROOT_PATH { redis_key.set_value(Vec::new(), val)?; redis_key.notify_keyspace_event(ctx, "json.set")?; manager.apply_changes(ctx); @@ -252,7 +245,7 @@ pub fn json_merge(manager: M, ctx: &Context, args: Vec) match current { Some(doc) => { - if path.get_path() == JSON_ROOT_PATH { + if path == *JSON_ROOT_PATH { redis_key.merge_value(Vec::new(), val)?; redis_key.notify_keyspace_event(ctx, "json.merge")?; manager.apply_changes(ctx); @@ -292,7 +285,7 @@ pub fn json_merge(manager: M, ctx: &Context, args: Vec) } } None => { - if path.get_path() == JSON_ROOT_PATH { + if path == *JSON_ROOT_PATH { // Nothing to merge with it's a new doc redis_key.set_value(Vec::new(), val)?; redis_key.notify_keyspace_event(ctx, "json.merge")?; @@ -327,7 +320,7 @@ pub fn json_mset(manager: M, ctx: &Context, args: Vec) // Verify the path is valid and get all the update info let path = Path::new(args.next_str()?); - let update_info = if path.get_path() == JSON_ROOT_PATH { + let update_info = if path == *JSON_ROOT_PATH { None } else if let Some(value) = key_value { Some(KeyValue::new(value).find_paths(path.get_path(), SetOptions::None)?) @@ -533,32 +526,33 @@ pub fn json_del(manager: M, ctx: &Context, args: Vec) - let key = args.next_arg()?; let path = match args.next() { - None => Path::new(default_path()), Some(s) => Path::new(s.try_as_str()?), + _ => Path::default(), }; let mut redis_key = manager.open_key_write(ctx, key)?; - let deleted = match redis_key.get_value()? { - Some(doc) => { - let res = if path.get_path() == JSON_ROOT_PATH { - redis_key.delete()?; - 1 - } else { - let mut paths = find_paths(path.get_path(), doc, |_| true)?; - prepare_paths_for_updating(&mut paths); - paths - .into_iter() - .try_fold(0, |acc, p| redis_key.delete_path(p).and(Ok(acc + 1)))? - }; - if res > 0 { - redis_key.notify_keyspace_event(ctx, "json.del")?; - manager.apply_changes(ctx); - } - res + let deleted = if let Some(doc) = redis_key.get_value()? { + let res = if path == *JSON_ROOT_PATH { + redis_key.delete()?; + 1 + } else { + let mut paths = find_paths(path.get_path(), doc, |_| true)?; + prepare_paths_for_updating(&mut paths); + paths.into_iter().try_fold(0i64, |acc, p| { + redis_key + .delete_path(p) + .map(|deleted| acc + if deleted { 1 } else { 0 }) + })? + }; + if res > 0 { + redis_key.notify_keyspace_event(ctx, "json.del")?; + manager.apply_changes(ctx); } - None => 0, + res + } else { + 0 }; - Ok((deleted as i64).into()) + Ok(deleted.into()) } /// @@ -612,7 +606,7 @@ pub fn json_mget(manager: M, ctx: &Context, args: Vec) pub fn json_type(manager: M, ctx: &Context, args: Vec) -> RedisResult { let mut args = args.into_iter().skip(1); let key = args.next_arg()?; - let path = Path::new(args.next_str().unwrap_or(default_path())); + let path = args.next_str().map(Path::new).unwrap_or_default(); let key = manager.open_key_read(ctx, &key)?; @@ -681,52 +675,29 @@ where let path = Path::new(args.next_str()?); let number = args.next_str()?; - let mut redis_key = manager.open_key_write(ctx, key)?; + let redis_key = manager.open_key_write(ctx, key)?; // check context flags to see if RESP3 is enabled if is_resp3(ctx) { - let res = json_num_op_impl::( - manager, - &mut redis_key, - ctx, - path.get_path(), - number, - op, - cmd, - )? - .into_iter() - .map(|v| { - v.map_or(RedisValue::Null, |v| { - if let Some(i) = v.as_i64() { - RedisValue::Integer(i) - } else { - RedisValue::Float(v.as_f64().unwrap_or_default()) - } + let res = json_num_op_impl::(manager, redis_key, ctx, path.get_path(), number, op, cmd)? + .into_iter() + .map(|v| { + v.map_or(RedisValue::Null, |v| { + if let Some(i) = v.as_i64() { + RedisValue::Integer(i) + } else { + RedisValue::Float(v.as_f64().unwrap_or_default()) + } + }) }) - }) - .collect_vec() - .into(); + .collect_vec() + .into(); Ok(res) } else if path.is_legacy() { - json_num_op_legacy::( - manager, - &mut redis_key, - ctx, - path.get_path(), - number, - op, - cmd, - ) + json_num_op_legacy::(manager, redis_key, ctx, path.get_path(), number, op, cmd) } else { - let results = json_num_op_impl::( - manager, - &mut redis_key, - ctx, - path.get_path(), - number, - op, - cmd, - )?; + let results = + json_num_op_impl::(manager, redis_key, ctx, path.get_path(), number, op, cmd)?; // Convert to RESP2 format return as one JSON array let values = to_json_value::(results, Value::Null); @@ -736,7 +707,7 @@ where fn json_num_op_impl( manager: M, - redis_key: &mut M::WriteHolder, + mut redis_key: M::WriteHolder, ctx: &Context, path: &str, number: &str, @@ -780,7 +751,7 @@ where fn json_num_op_legacy( manager: M, - redis_key: &mut M::WriteHolder, + mut redis_key: M::WriteHolder, ctx: &Context, path: &str, number: &str, @@ -859,18 +830,18 @@ pub fn json_bool_toggle( let mut args = args.into_iter().skip(1); let key = args.next_arg()?; let path = Path::new(args.next_str()?); - let mut redis_key = manager.open_key_write(ctx, key)?; + let redis_key = manager.open_key_write(ctx, key)?; if path.is_legacy() { - json_bool_toggle_legacy::(manager, &mut redis_key, ctx, path.get_path()) + json_bool_toggle_legacy::(manager, redis_key, ctx, path.get_path()) } else { - json_bool_toggle_impl::(manager, &mut redis_key, ctx, path.get_path()) + json_bool_toggle_impl::(manager, redis_key, ctx, path.get_path()) } } fn json_bool_toggle_impl( manager: M, - redis_key: &mut M::WriteHolder, + mut redis_key: M::WriteHolder, ctx: &Context, path: &str, ) -> RedisResult @@ -901,7 +872,7 @@ where fn json_bool_toggle_legacy( manager: M, - redis_key: &mut M::WriteHolder, + mut redis_key: M::WriteHolder, ctx: &Context, path: &str, ) -> RedisResult @@ -948,22 +919,22 @@ pub fn json_str_append( path = Path::new(path_or_json); json = val.try_as_str()?; } else { - path = Path::new(default_path()); + path = Path::default(); json = path_or_json; } - let mut redis_key = manager.open_key_write(ctx, key)?; + let redis_key = manager.open_key_write(ctx, key)?; if path.is_legacy() { - json_str_append_legacy::(manager, &mut redis_key, ctx, path.get_path(), json) + json_str_append_legacy::(manager, redis_key, ctx, path.get_path(), json) } else { - json_str_append_impl::(manager, &mut redis_key, ctx, path.get_path(), json) + json_str_append_impl::(manager, redis_key, ctx, path.get_path(), json) } } fn json_str_append_impl( manager: M, - redis_key: &mut M::WriteHolder, + mut redis_key: M::WriteHolder, ctx: &Context, path: &str, json: &str, @@ -997,7 +968,7 @@ where fn json_str_append_legacy( manager: M, - redis_key: &mut M::WriteHolder, + mut redis_key: M::WriteHolder, ctx: &Context, path: &str, json: &str, @@ -1031,7 +1002,7 @@ where pub fn json_str_len(manager: M, ctx: &Context, args: Vec) -> RedisResult { let mut args = args.into_iter().skip(1); let key = args.next_arg()?; - let path = Path::new(args.next_str().unwrap_or(default_path())); + let path = args.next_str().map(Path::new).unwrap_or_default(); let key = manager.open_key_read(ctx, &key)?; @@ -1083,27 +1054,26 @@ pub fn json_arr_append( // We require at least one JSON item to append args.peek().ok_or(RedisError::WrongArity)?; - let args = args.try_fold::<_, _, Result<_, RedisError>>( - Vec::with_capacity(args.len()), - |mut acc, arg| { + let args = args + .map(|arg| -> Result<_, RedisError> { let json = arg.try_as_str()?; - acc.push(manager.from_str(json, Format::JSON, true)?); - Ok(acc) - }, - )?; + let sv_holder = manager.from_str(json, Format::JSON, true)?; + Ok(sv_holder) + }) + .try_collect()?; - let mut redis_key = manager.open_key_write(ctx, key)?; + let redis_key = manager.open_key_write(ctx, key)?; if path.is_legacy() { - json_arr_append_legacy::(manager, &mut redis_key, ctx, &path, args) + json_arr_append_legacy::(manager, redis_key, ctx, &path, args) } else { - json_arr_append_impl::(manager, &mut redis_key, ctx, path.get_path(), args) + json_arr_append_impl::(manager, redis_key, ctx, path.get_path(), args) } } fn json_arr_append_legacy( manager: M, - redis_key: &mut M::WriteHolder, + mut redis_key: M::WriteHolder, ctx: &Context, path: &Path, args: Vec, @@ -1139,7 +1109,7 @@ where fn json_arr_append_impl( manager: M, - redis_key: &mut M::WriteHolder, + mut redis_key: M::WriteHolder, ctx: &Context, path: &str, args: Vec, @@ -1248,25 +1218,25 @@ pub fn json_arr_insert( // We require at least one JSON item to insert args.peek().ok_or(RedisError::WrongArity)?; - let args = args.try_fold::<_, _, Result<_, RedisError>>( - Vec::with_capacity(args.len()), - |mut acc, arg| { + let args = args + .map(|arg| -> Result<_, RedisError> { let json = arg.try_as_str()?; - acc.push(manager.from_str(json, Format::JSON, true)?); - Ok(acc) - }, - )?; - let mut redis_key = manager.open_key_write(ctx, key)?; + let sv_holder = manager.from_str(json, Format::JSON, true)?; + Ok(sv_holder) + }) + .try_collect()?; + + let redis_key = manager.open_key_write(ctx, key)?; if path.is_legacy() { - json_arr_insert_legacy::(manager, &mut redis_key, ctx, path.get_path(), index, args) + json_arr_insert_legacy::(manager, redis_key, ctx, path.get_path(), index, args) } else { - json_arr_insert_impl::(manager, &mut redis_key, ctx, path.get_path(), index, args) + json_arr_insert_impl::(manager, redis_key, ctx, path.get_path(), index, args) } } fn json_arr_insert_impl( manager: M, - redis_key: &mut M::WriteHolder, + mut redis_key: M::WriteHolder, ctx: &Context, path: &str, index: i64, @@ -1302,7 +1272,7 @@ where fn json_arr_insert_legacy( manager: M, - redis_key: &mut M::WriteHolder, + mut redis_key: M::WriteHolder, ctx: &Context, path: &str, index: i64, @@ -1337,7 +1307,7 @@ where pub fn json_arr_len(manager: M, ctx: &Context, args: Vec) -> RedisResult { let mut args = args.into_iter().skip(1); let key = manager.open_key_read(ctx, &args.next_arg()?)?; - let path = Path::new(args.next_str().unwrap_or(default_path())); + let path = args.next_str().map(Path::new).unwrap_or_default(); if path.is_legacy() { json_arr_len_legacy::(key, path) } else { @@ -1424,7 +1394,7 @@ pub fn json_arr_pop(manager: M, ctx: &Context, args: Vec (Path::new(default_path()), i64::MAX), + None => (Path::default(), i64::MAX), Some(s) => { let path = Path::new(s.try_as_str()?); let index = args.next_i64().unwrap_or(-1); @@ -1433,7 +1403,7 @@ pub fn json_arr_pop(manager: M, ctx: &Context, args: Vec(manager: M, ctx: &Context, args: Vec(manager, &mut redis_key, ctx, path.get_path(), index) + json_arr_pop_legacy::(manager, redis_key, ctx, path.get_path(), index) } else { json_arr_pop_impl::( manager, - &mut redis_key, + redis_key, ctx, path.get_path(), index, @@ -1456,7 +1426,7 @@ pub fn json_arr_pop(manager: M, ctx: &Context, args: Vec( manager: M, - redis_key: &mut M::WriteHolder, + mut redis_key: M::WriteHolder, ctx: &Context, path: &str, index: i64, @@ -1496,7 +1466,7 @@ where fn json_arr_pop_legacy( manager: M, - redis_key: &mut M::WriteHolder, + mut redis_key: M::WriteHolder, ctx: &Context, path: &str, index: i64, @@ -1538,17 +1508,17 @@ pub fn json_arr_trim(manager: M, ctx: &Context, args: Vec(manager, &mut redis_key, ctx, path.get_path(), start, stop) + json_arr_trim_legacy::(manager, redis_key, ctx, path.get_path(), start, stop) } else { - json_arr_trim_impl::(manager, &mut redis_key, ctx, path.get_path(), start, stop) + json_arr_trim_impl::(manager, redis_key, ctx, path.get_path(), start, stop) } } fn json_arr_trim_impl( manager: M, - redis_key: &mut M::WriteHolder, + mut redis_key: M::WriteHolder, ctx: &Context, path: &str, start: i64, @@ -1582,7 +1552,7 @@ where fn json_arr_trim_legacy( manager: M, - redis_key: &mut M::WriteHolder, + mut redis_key: M::WriteHolder, ctx: &Context, path: &str, start: i64, @@ -1617,17 +1587,17 @@ where pub fn json_obj_keys(manager: M, ctx: &Context, args: Vec) -> RedisResult { let mut args = args.into_iter().skip(1); let key = args.next_arg()?; - let path = Path::new(args.next_str().unwrap_or(default_path())); + let path = args.next_str().map(Path::new).unwrap_or_default(); - let mut key = manager.open_key_read(ctx, &key)?; + let key = manager.open_key_read(ctx, &key)?; if path.is_legacy() { - json_obj_keys_legacy::(&mut key, path.get_path()) + json_obj_keys_legacy::(key, path.get_path()) } else { - json_obj_keys_impl::(&mut key, path.get_path()) + json_obj_keys_impl::(key, path.get_path()) } } -fn json_obj_keys_impl(redis_key: &mut M::ReadHolder, path: &str) -> RedisResult +fn json_obj_keys_impl(redis_key: M::ReadHolder, path: &str) -> RedisResult where M: Manager, { @@ -1645,7 +1615,7 @@ where Ok(res) } -fn json_obj_keys_legacy(redis_key: &mut M::ReadHolder, path: &str) -> RedisResult +fn json_obj_keys_legacy(redis_key: M::ReadHolder, path: &str) -> RedisResult where M: Manager, { @@ -1673,8 +1643,7 @@ where pub fn json_obj_len(manager: M, ctx: &Context, args: Vec) -> RedisResult { let mut args = args.into_iter().skip(1); let key = args.next_arg()?; - - let path = Path::new(args.next_str().unwrap_or(default_path())); + let path = args.next_str().map(Path::new).unwrap_or_default(); let key = manager.open_key_read(ctx, &key)?; if path.is_legacy() { @@ -1727,22 +1696,11 @@ where pub fn json_clear(manager: M, ctx: &Context, args: Vec) -> RedisResult { let mut args = args.into_iter().skip(1); let key = args.next_arg()?; - let paths = args.try_fold::<_, _, Result, RedisError>>( - Vec::with_capacity(args.len()), - |mut acc, arg| { - let s = arg.try_as_str()?; - acc.push(Path::new(s)); - Ok(acc) - }, - )?; - - let paths = if paths.is_empty() { - vec![Path::new(default_path())] - } else { - paths + let path = match args.next() { + Some(s) => Path::new(s.try_as_str()?), + _ => Path::default(), }; - - let path = paths.first().unwrap().get_path(); + let path = path.get_path(); let mut redis_key = manager.open_key_write(ctx, key)?; @@ -1756,12 +1714,9 @@ pub fn json_clear(manager: M, ctx: &Context, args: Vec) SelectValueType::Double => v.get_double() != 0.0, _ => false, })?; - let mut cleared = 0; - if !paths.is_empty() { - for p in paths { - cleared += redis_key.clear(p)?; - } - } + let cleared = paths + .into_iter() + .try_fold(0, |acc, p| redis_key.clear(p).map(|cleared| acc + cleared))?; if cleared > 0 { redis_key.notify_keyspace_event(ctx, "json.clear")?; manager.apply_changes(ctx); @@ -1781,7 +1736,7 @@ pub fn json_debug(manager: M, ctx: &Context, args: Vec) match args.next_str()?.to_uppercase().as_str() { "MEMORY" => { let key = args.next_arg()?; - let path = Path::new(args.next_str().unwrap_or(default_path())); + let path = args.next_str().map(Path::new).unwrap_or_default(); let key = manager.open_key_read(ctx, &key)?; if path.is_legacy() { @@ -1825,7 +1780,7 @@ pub fn json_resp(manager: M, ctx: &Context, args: Vec) let key = args.next_arg()?; let path = match args.next() { - None => Path::new(default_path()), + None => Path::default(), Some(s) => Path::new(s.try_as_str()?), }; diff --git a/redis_json/src/redisjson.rs b/redis_json/src/redisjson.rs index e3ff3f8ae..1faff1b4d 100644 --- a/redis_json/src/redisjson.rs +++ b/redis_json/src/redisjson.rs @@ -18,12 +18,16 @@ use crate::backward; use crate::error::Error; use crate::ivalue_manager::RedisIValueJsonKeyManager; use crate::manager::Manager; +use once_cell::unsync::Lazy; use serde::Serialize; use std::fmt; use std::fmt::Display; use std::marker::PhantomData; use std::str::FromStr; +const JSON_ROOT_PATH_LEGACY: &str = "."; +pub const JSON_ROOT_PATH: Lazy = Lazy::new(|| Path::new("$")); + /// Returns normalized start index #[must_use] pub fn normalize_arr_start_index(start: i64, len: i64) -> i64 { @@ -152,6 +156,21 @@ impl Display for Path<'_> { } } +/// Returns the default path for the given RESP version +impl Default for Path<'_> { + fn default() -> Self { + Self::new(JSON_ROOT_PATH_LEGACY) + } +} + +impl PartialEq for Path<'_> { + fn eq(&self, other: &Self) -> bool { + self.get_path() == other.get_path() + } +} + +impl Eq for Path<'_> {} + #[derive(Debug)] pub struct RedisJSON { //FIXME: make private and expose array/object Values without requiring a path From 3b9eb5acc8af65d449e24a77d27f593c4ac62d47 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Sun, 11 Aug 2024 17:58:10 +0300 Subject: [PATCH 03/33] refactor --- redis_json/src/commands.rs | 189 +++++++++++++++---------------- redis_json/src/ivalue_manager.rs | 72 +++++------- 2 files changed, 125 insertions(+), 136 deletions(-) diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index 905352add..593102f55 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -1479,21 +1479,21 @@ where .ok_or_else(RedisError::nonexistent_key)?; let paths = find_paths(path, root, |v| v.get_type() == SelectValueType::Array)?; - if !paths.is_empty() { - let mut res = Ok(().into()); - for p in paths { - res = Ok(redis_key.arr_pop(p, index, |v| match v { - Some(r) => Ok(serde_json::to_string(&r)?.into()), - None => Ok(().into()), - })?); - } - redis_key.notify_keyspace_event(ctx, "json.arrpop")?; - manager.apply_changes(ctx); - res - } else { + if paths.is_empty() { Err(RedisError::String( err_msg_json_path_doesnt_exist_with_param_or(path, "not an array"), )) + } else { + let res = paths.into_iter().try_fold(RedisValue::Null, |_, p| { + redis_key.arr_pop(p, index, |v| { + v.map_or(Ok(RedisValue::Null), |r| { + Ok(serde_json::to_string(&r)?.into()) + }) + }) + }); + redis_key.notify_keyspace_event(ctx, "json.arrpop")?; + manager.apply_changes(ctx); + res } } @@ -1531,18 +1531,19 @@ where .get_value()? .ok_or_else(RedisError::nonexistent_key)?; - let paths = find_all_paths(path, root, |v| v.get_type() == SelectValueType::Array)?; - let mut res: Vec = vec![]; let mut need_notify = false; - for p in paths { - res.push(match p { - Some(p) => { - need_notify = true; - (redis_key.arr_trim(p, start, stop)?).into() - } - _ => RedisValue::Null, - }); - } + let res: Vec<_> = find_all_paths(path, root, |v| v.get_type() == SelectValueType::Array) + .and_then(|paths| { + paths + .into_iter() + .map(|p| { + p.map_or(Ok(RedisValue::Null), |p| { + need_notify = true; + redis_key.arr_trim(p, start, stop).map(Into::into) + }) + }) + .try_collect() + })?; if need_notify { redis_key.notify_keyspace_event(ctx, "json.arrtrim")?; manager.apply_changes(ctx); @@ -1571,13 +1572,14 @@ where err_msg_json_path_doesnt_exist_with_param_or(path, "not an array"), )) } else { - let mut res = None; - for p in paths { - res = Some(redis_key.arr_trim(p, start, stop)?); - } - redis_key.notify_keyspace_event(ctx, "json.arrtrim")?; - manager.apply_changes(ctx); - Ok(res.unwrap().into()) + paths + .into_iter() + .try_fold(0, |_, p| redis_key.arr_trim(p, start, stop)) + .and_then(|res| { + redis_key.notify_keyspace_event(ctx, "json.arrtrim")?; + manager.apply_changes(ctx); + Ok(res.into()) + }) } } @@ -1604,15 +1606,13 @@ where let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; - let res: RedisValue = { - let values = find_all_values(path, root, |v| v.get_type() == SelectValueType::Object)?; - let mut res: Vec = vec![]; - for v in values { - res.push(v.map_or(RedisValue::Null, |v| v.keys().unwrap().collect_vec().into())); - } - res.into() - }; - Ok(res) + + find_all_values(path, root, |v| v.get_type() == SelectValueType::Object).map(|v| { + v.into_iter() + .map(|v| v.map_or(RedisValue::Null, |v| v.keys().unwrap().collect_vec().into())) + .collect_vec() + .into() + }) } fn json_obj_keys_legacy(redis_key: M::ReadHolder, path: &str) -> RedisResult @@ -1623,18 +1623,14 @@ where Some(v) => v, _ => return Ok(RedisValue::Null), }; - let value = match KeyValue::new(root).get_first(path) { - Ok(v) => match v.get_type() { - SelectValueType::Object => v.keys().unwrap().collect_vec().into(), - _ => { - return Err(RedisError::String( - err_msg_json_path_doesnt_exist_with_param_or(path, "not an object"), - )) - } - }, - _ => RedisValue::Null, - }; - Ok(value) + KeyValue::new(root) + .get_first(path) + .map_or(Ok(RedisValue::Null), |v| match v.get_type() { + SelectValueType::Object => Ok(v.keys().unwrap().collect_vec().into()), + _ => Err(RedisError::String( + err_msg_json_path_doesnt_exist_with_param_or(path, "not an object"), + )), + }) } /// @@ -1647,37 +1643,38 @@ pub fn json_obj_len(manager: M, ctx: &Context, args: Vec(&key, path.get_path()) + json_obj_len_legacy::(key, path.get_path()) } else { - json_obj_len_impl::(&key, path.get_path()) + json_obj_len_impl::(key, path.get_path()) } } -fn json_obj_len_impl(redis_key: &M::ReadHolder, path: &str) -> RedisResult +fn json_obj_len_impl(redis_key: M::ReadHolder, path: &str) -> RedisResult where M: Manager, { - let root = redis_key.get_value()?; - let res = match root { - Some(root) => find_all_values(path, root, |v| v.get_type() == SelectValueType::Object)? - .iter() - .map(|v| { - v.map_or(RedisValue::Null, |v| { - RedisValue::Integer(v.len().unwrap() as i64) - }) - }) - .collect_vec() - .into(), - None => { - return Err(RedisError::String( + redis_key.get_value()?.map_or_else( + || { + Err(RedisError::String( err_msg_json_path_doesnt_exist_with_param_or(path, "not an object"), )) - } - }; - Ok(res) + }, + |root| { + find_all_values(path, root, |v| v.get_type() == SelectValueType::Object).map(|v| { + v.into_iter() + .map(|v| { + v.map_or(RedisValue::Null, |v| { + RedisValue::Integer(v.len().unwrap() as _) + }) + }) + .collect_vec() + .into() + }) + }, + ) } -fn json_obj_len_legacy(redis_key: &M::ReadHolder, path: &str) -> RedisResult +fn json_obj_len_legacy(redis_key: M::ReadHolder, path: &str) -> RedisResult where M: Manager, { @@ -1714,14 +1711,16 @@ pub fn json_clear(manager: M, ctx: &Context, args: Vec) SelectValueType::Double => v.get_double() != 0.0, _ => false, })?; - let cleared = paths + paths .into_iter() - .try_fold(0, |acc, p| redis_key.clear(p).map(|cleared| acc + cleared))?; - if cleared > 0 { - redis_key.notify_keyspace_event(ctx, "json.clear")?; - manager.apply_changes(ctx); - } - Ok(cleared.into()) + .try_fold(0, |acc, p| redis_key.clear(p).map(|cleared| acc + cleared)) + .and_then(|cleared| { + if cleared > 0 { + redis_key.notify_keyspace_event(ctx, "json.clear")?; + manager.apply_changes(ctx); + } + Ok(cleared.into()) + }) } /// @@ -1737,26 +1736,26 @@ pub fn json_debug(manager: M, ctx: &Context, args: Vec) "MEMORY" => { let key = args.next_arg()?; let path = args.next_str().map(Path::new).unwrap_or_default(); - let key = manager.open_key_read(ctx, &key)?; + if path.is_legacy() { - Ok(match key.get_value()? { - Some(doc) => { - manager.get_memory(KeyValue::new(doc).get_first(path.get_path())?)? - } - None => 0, - } - .into()) + key.get_value() + .transpose() + .map_or(Ok(0), |doc| { + manager.get_memory(KeyValue::new(doc?).get_first(path.get_path())?) + }) + .map(Into::into) } else { - Ok(match key.get_value()? { - Some(doc) => KeyValue::new(doc) - .get_values(path.get_path())? - .iter() - .map(|v| manager.get_memory(v).unwrap()) - .collect(), - None => vec![], - } - .into()) + key.get_value() + .transpose() + .map_or(Ok(vec![]), |doc| { + KeyValue::new(doc?) + .get_values(path.get_path())? + .into_iter() + .map(|v| manager.get_memory(v)) + .try_collect() + }) + .map(Into::into) } } "HELP" => { diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index 32700b0b4..6eca10784 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -168,17 +168,16 @@ impl<'a> IValueKeyHolderWrite<'a> { where F: FnMut(&mut IValue) -> Result, Error>, { + let root = self.get_value().unwrap().unwrap(); if paths.is_empty() { // updating the root require special treatment - let root = self.get_value().unwrap().unwrap(); - if op_fun(root) + op_fun(root) .map_err(|err| RedisError::String(err.msg))? - .is_none() - { - root.take(); - } + .unwrap_or_else(|| { + root.take(); + }); } else { - update(paths, self.get_value().unwrap().unwrap(), op_fun)?; + update(paths, root, op_fun)?; } Ok(()) @@ -267,25 +266,20 @@ impl<'a> IValueKeyHolderWrite<'a> { impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { fn notify_keyspace_event(&mut self, ctx: &Context, command: &str) -> Result<(), RedisError> { - if ctx.notify_keyspace_event(NotifyEvent::MODULE, command, &self.key_name) != Status::Ok { - Err(RedisError::Str("failed notify key space event")) - } else { - Ok(()) + match ctx.notify_keyspace_event(NotifyEvent::MODULE, command, &self.key_name) { + Status::Ok => Ok(()), + Status::Err => Err(RedisError::Str("failed notify key space event")), } } fn delete(&mut self) -> Result<(), RedisError> { - self.key.delete()?; - Ok(()) + self.key.delete().and(Ok(())) } fn get_value(&mut self) -> Result, RedisError> { self.get_json_holder()?; - - match &mut self.val { - Some(v) => Ok(Some(&mut v.data)), - None => Ok(None), - } + let val = self.val.as_mut().map(|v| &mut v.data); + Ok(val) } fn set_value(&mut self, path: Vec, mut v: IValue) -> Result { @@ -529,8 +523,11 @@ pub struct IValueKeyHolderRead { impl ReadHolder for IValueKeyHolderRead { fn get_value(&self) -> Result, RedisError> { - let key_value = self.key.get_value::>(&REDIS_JSON_TYPE)?; - key_value.map_or(Ok(None), |v| Ok(Some(&v.data))) + let data = self + .key + .get_value::>(&REDIS_JSON_TYPE)? + .map(|v| &v.data); + Ok(data) } } @@ -615,23 +612,19 @@ impl<'a> Manager for RedisIValueJsonKeyManager<'a> { Document::from_reader(&mut Cursor::new(val.as_bytes())) .map_err(|e| e.to_string())?, ) - .map_or_else( - |e| Err(e.to_string().into()), - |docs: Document| { - let v = docs.iter().next().map_or(IValue::NULL, |(_, b)| { - let v: serde_json::Value = b.clone().into(); - let mut out = serde_json::Serializer::new(Vec::new()); - v.serialize(&mut out).unwrap(); - self.from_str( - &String::from_utf8(out.into_inner()).unwrap(), - Format::JSON, - limit_depth, - ) - .unwrap() - }); - Ok(v) - }, - ), + .map_err(|e| e.to_string().into()) + .and_then(|docs: Document| { + docs.iter().next().map_or(Ok(IValue::NULL), |(_, b)| { + let v: serde_json::Value = b.clone().into(); + let mut out = serde_json::Serializer::new(Vec::new()); + v.serialize(&mut out).unwrap(); + self.from_str( + &String::from_utf8(out.into_inner()).unwrap(), + Format::JSON, + limit_depth, + ) + }) + }), } } @@ -690,10 +683,7 @@ impl<'a> Manager for RedisIValueJsonKeyManager<'a> { } fn is_json(&self, key: *mut RedisModuleKey) -> Result { - match verify_type(key, &REDIS_JSON_TYPE) { - Ok(_) => Ok(true), - Err(_) => Ok(false), - } + Ok(verify_type(key, &REDIS_JSON_TYPE).is_ok()) } } From 1eb8567d8dbc9d96d85530ca3532c37ba4d8f272 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Mon, 12 Aug 2024 14:27:36 +0300 Subject: [PATCH 04/33] into_both --- redis_json/src/backward.rs | 47 ++++++++++++++------------------ redis_json/src/commands.rs | 20 +++++++------- redis_json/src/ivalue_manager.rs | 2 +- redis_json/src/key_value.rs | 13 ++++----- redis_json/src/manager.rs | 2 +- redis_json/src/redisjson.rs | 17 ++++++++++++ 6 files changed, 56 insertions(+), 45 deletions(-) diff --git a/redis_json/src/backward.rs b/redis_json/src/backward.rs index 624a0c057..f21f737d1 100644 --- a/redis_json/src/backward.rs +++ b/redis_json/src/backward.rs @@ -1,17 +1,16 @@ -/* - * Copyright Redis Ltd. 2016 - present - * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or - * the Server Side Public License v1 (SSPLv1). - */ - -use std::vec::Vec; +/* + * Copyright Redis Ltd. 2016 - present + * Licensed under your choice of the Redis Source Available License 2.0 (RSALv2) or + * the Server Side Public License v1 (SSPLv1). + */ +use itertools::Itertools; use redis_module::raw; -use serde_json::map::Map; use serde_json::Number; use serde_json::Value; use crate::error::Error; +use crate::redisjson::ResultInto; #[derive(Debug, PartialEq)] enum NodeType { @@ -58,34 +57,30 @@ pub fn json_rdb_load(rdb: *mut raw::RedisModuleIO) -> Result { } NodeType::Number => { let n = raw::load_double(rdb)?; - Ok(Value::Number( - Number::from_f64(n).ok_or_else(|| Error::from("Can't load as float"))?, - )) + Number::from_f64(n) + .ok_or_else(|| Error::from("Can't load as float")) + .into_both() } NodeType::String => { let buffer = raw::load_string_buffer(rdb)?; - Ok(Value::String(buffer.to_string()?)) + buffer.to_string().into_both() } NodeType::Dict => { let len = raw::load_unsigned(rdb)?; - let mut m = Map::with_capacity(len as usize); - for _ in 0..len { - let t: NodeType = raw::load_unsigned(rdb)?.into(); - if t != NodeType::KeyVal { - return Err(Error::from("Can't load old RedisJSON RDB")); - } - let buffer = raw::load_string_buffer(rdb)?; - m.insert(buffer.to_string()?, json_rdb_load(rdb)?); - } + let m = (0..len) + .map(|_| match raw::load_unsigned(rdb)?.into() { + NodeType::KeyVal => { + let buffer = raw::load_string_buffer(rdb)?; + Ok((buffer.to_string()?, json_rdb_load(rdb)?)) + } + _ => Err(Error::from("Can't load old RedisJSON RDB")), + }) + .try_collect()?; Ok(Value::Object(m)) } NodeType::Array => { let len = raw::load_unsigned(rdb)?; - let mut v = Vec::with_capacity(len as usize); - for _ in 0..len { - let nested = json_rdb_load(rdb)?; - v.push(nested); - } + let v = (0..len).map(|_| json_rdb_load(rdb)).try_collect()?; Ok(Value::Array(v)) } NodeType::KeyVal => Err(Error::from("Can't load old RedisJSON RDB")), diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index 593102f55..92fbef710 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -11,7 +11,7 @@ use crate::manager::{ err_msg_json_path_doesnt_exist_with_param, err_msg_json_path_doesnt_exist_with_param_or, Manager, ReadHolder, UpdateInfo, WriteHolder, }; -use crate::redisjson::{Format, Path, ReplyFormat, SetOptions, JSON_ROOT_PATH}; +use crate::redisjson::{Format, Path, ReplyFormat, ResultInto, SetOptions, JSON_ROOT_PATH}; use json_path::select_value::{SelectValue, SelectValueType}; use redis_module::{Context, RedisValue}; use redis_module::{NextArg, RedisError, RedisResult, RedisString, REDIS_OK}; @@ -632,8 +632,8 @@ where let value = match root { Some(root) => KeyValue::new(root) .get_values(path)? - .iter() - .map(|v| RedisValue::from(KeyValue::value_name(*v))) + .into_iter() + .map(|v| RedisValue::from(KeyValue::value_name(v))) .collect_vec() .into(), None => RedisValue::Null, @@ -645,11 +645,11 @@ fn json_type_legacy(redis_key: &M::ReadHolder, path: &str) -> RedisResult where M: Manager, { - let value = redis_key.get_value()?.map_or(RedisValue::Null, |doc| { - KeyValue::new(doc) - .get_type(path) - .map_or(RedisValue::Null, |s| s.into()) - }); + let value = redis_key + .get_value()? + .map(|doc| KeyValue::new(doc).get_type(path).map(|s| s.into()).ok()) + .flatten() + .unwrap_or(RedisValue::Null); Ok(value) } @@ -1450,7 +1450,7 @@ where if format_options.is_resp3_reply() { Ok(KeyValue::value_to_resp3(v, format_options)) } else { - Ok(serde_json::to_string(&v)?.into()) + serde_json::to_string(&v).into_both() } }) })?, @@ -1487,7 +1487,7 @@ where let res = paths.into_iter().try_fold(RedisValue::Null, |_, p| { redis_key.arr_pop(p, index, |v| { v.map_or(Ok(RedisValue::Null), |r| { - Ok(serde_json::to_string(&r)?.into()) + serde_json::to_string(&r).into_both() }) }) }); diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index 6eca10784..eb68bbba4 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -265,7 +265,7 @@ impl<'a> IValueKeyHolderWrite<'a> { } impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { - fn notify_keyspace_event(&mut self, ctx: &Context, command: &str) -> Result<(), RedisError> { + fn notify_keyspace_event(self, ctx: &Context, command: &str) -> Result<(), RedisError> { match ctx.notify_keyspace_event(NotifyEvent::MODULE, command, &self.key_name) { Status::Ok => Ok(()), Status::Err => Err(RedisError::Str("failed notify key space event")), diff --git a/redis_json/src/key_value.rs b/redis_json/src/key_value.rs index 73b2a09bc..b58df53f5 100644 --- a/redis_json/src/key_value.rs +++ b/redis_json/src/key_value.rs @@ -18,7 +18,7 @@ use crate::{ err_msg_json_expected, err_msg_json_path_doesnt_exist_with_param, AddUpdateInfo, SetUpdateInfo, UpdateInfo, }, - redisjson::{normalize_arr_indices, Path, ReplyFormat, SetOptions}, + redisjson::{normalize_arr_indices, Path, ReplyFormat, ResultInto, SetOptions}, }; pub struct KeyValue<'a, V: SelectValue> { @@ -179,15 +179,14 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { format: ReplyFormatOptions, is_legacy: bool, ) -> Result { - let res = if is_legacy { - self.to_string_single(path, format)?.into() + if is_legacy { + self.to_string_single(path, format).into_both() } else if format.is_resp3_reply() { let values = self.get_values(path)?; - Self::values_to_resp3(values, format) + Ok(Self::values_to_resp3(values, format)) } else { - self.to_string_multi(path, format)?.into() - }; - Ok(res) + self.to_string_multi(path, format).into_both() + } } fn values_to_resp3(values: Vec<&V>, format: ReplyFormatOptions) -> RedisValue { diff --git a/redis_json/src/manager.rs b/redis_json/src/manager.rs index 96b782163..a90e7e8ff 100644 --- a/redis_json/src/manager.rs +++ b/redis_json/src/manager.rs @@ -63,7 +63,7 @@ pub trait WriteHolder { ) -> RedisResult; fn arr_trim(&mut self, path: Vec, start: i64, stop: i64) -> Result; fn clear(&mut self, path: Vec) -> Result; - fn notify_keyspace_event(&mut self, ctx: &Context, command: &str) -> Result<(), RedisError>; + fn notify_keyspace_event(self, ctx: &Context, command: &str) -> Result<(), RedisError>; } pub trait Manager { diff --git a/redis_json/src/redisjson.rs b/redis_json/src/redisjson.rs index 1faff1b4d..5ccb13ace 100644 --- a/redis_json/src/redisjson.rs +++ b/redis_json/src/redisjson.rs @@ -177,6 +177,23 @@ pub struct RedisJSON { pub data: T, } +pub(crate) trait ResultInto { + fn into_both(self) -> Result; +} + +impl ResultInto for Result +where + S: From, + F: From, +{ + fn into_both(self) -> Result { + match self { + Ok(ok) => Ok(ok.into()), + Err(e) => Err(e.into()), + } + } +} + pub mod type_methods { use super::*; use std::{ffi::CString, ptr::null_mut}; From ca90719c734c7ed36b6d67139e07aa289481644a Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Tue, 13 Aug 2024 17:08:31 +0300 Subject: [PATCH 05/33] some work on `do_op` --- json_path/src/json_node.rs | 20 +- json_path/src/json_path.rs | 4 +- json_path/src/lib.rs | 6 +- json_path/src/select_value.rs | 4 +- redis_json/src/commands.rs | 42 ++- redis_json/src/ivalue_manager.rs | 138 ++++---- redis_json/src/key_value.rs | 2 +- test.out | 526 +++++++++++++++++++++++++++++++ tests/pytest/test.py | 3 + 9 files changed, 629 insertions(+), 116 deletions(-) create mode 100644 test.out diff --git a/json_path/src/json_node.rs b/json_path/src/json_node.rs index 22193de56..9d33b620c 100644 --- a/json_path/src/json_node.rs +++ b/json_path/src/json_node.rs @@ -38,16 +38,16 @@ impl SelectValue for Value { } } - fn keys<'a>(&'a self) -> Option + 'a>> { + fn keys(&self) -> Option> { match self { - Self::Object(o) => Some(Box::new(o.keys().map(|k| &k[..]))), + Self::Object(o) => Some(o.keys().map(|k| &k[..])), _ => None, } } - fn items<'a>(&'a self) -> Option + 'a>> { + fn items(&self) -> Option> { match self { - Self::Object(o) => Some(Box::new(o.iter().map(|(k, v)| (&k[..], v)))), + Self::Object(o) => Some(o.iter().map(|(k, v)| (&k[..], v))), _ => None, } } @@ -169,16 +169,12 @@ impl SelectValue for IValue { } } - fn keys<'a>(&'a self) -> Option + 'a>> { - self.as_object() - .map_or(None, |o| Some(Box::new(o.keys().map(|k| &k[..])))) + fn keys(&self) -> Option> { + self.as_object().map(|o| o.keys().map(|k| &k[..])) } - fn items<'a>(&'a self) -> Option + 'a>> { - match self.as_object() { - Some(o) => Some(Box::new(o.iter().map(|(k, v)| (&k[..], v)))), - _ => None, - } + fn items(&self) -> Option> { + self.as_object().map(|o| o.iter().map(|(k, v)| (&k[..], v))) } fn len(&self) -> Option { diff --git a/json_path/src/json_path.rs b/json_path/src/json_path.rs index 427e42cbe..4ac0715ba 100644 --- a/json_path/src/json_path.rs +++ b/json_path/src/json_path.rs @@ -440,7 +440,7 @@ impl<'i, 'j, S: SelectValue> TermEvaluationResult<'i, 'j, S> { } fn re_is_match(regex: &str, s: &str) -> bool { - Regex::new(regex).map_or_else(|_| false, |re| Regex::is_match(&re, s)) + Regex::new(regex).map_or(false, |re| Regex::is_match(&re, s)) } fn re_match(&self, s: &Self) -> bool { @@ -1060,7 +1060,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { } else { self.calc_internal(root, json, None, &mut calc_data); } - calc_data.results.drain(..).collect() + calc_data.results } pub fn calc_with_paths<'j: 'i, S: SelectValue>( diff --git a/json_path/src/lib.rs b/json_path/src/lib.rs index 6381b98b2..5f84c9d68 100644 --- a/json_path/src/lib.rs +++ b/json_path/src/lib.rs @@ -9,8 +9,8 @@ pub mod json_path; pub mod select_value; use crate::json_path::{ - CalculationResult, DummyTracker, DummyTrackerGenerator, PTracker, PTrackerGenerator, - PathCalculator, Query, QueryCompilationError, UserPathTracker, + CalculationResult, DummyTrackerGenerator, PTracker, PTrackerGenerator, PathCalculator, Query, + QueryCompilationError, UserPathTracker, }; use crate::select_value::SelectValue; @@ -78,7 +78,7 @@ pub fn calc_once<'j, 'p, S: SelectValue>(q: Query<'j>, json: &'p S) -> Vec<&'p S } .calc_with_paths_on_root(json, root) .into_iter() - .map(|e: CalculationResult<'p, S, DummyTracker>| e.res) + .map(|e| e.res) .collect() } diff --git a/json_path/src/select_value.rs b/json_path/src/select_value.rs index 243c029dd..86d61e8bb 100644 --- a/json_path/src/select_value.rs +++ b/json_path/src/select_value.rs @@ -22,8 +22,8 @@ pub trait SelectValue: Debug + Eq + PartialEq + Default + Clone + Serialize { fn get_type(&self) -> SelectValueType; fn contains_key(&self, key: &str) -> bool; fn values<'a>(&'a self) -> Option + 'a>>; - fn keys<'a>(&'a self) -> Option + 'a>>; - fn items<'a>(&'a self) -> Option + 'a>>; + fn keys(&self) -> Option>; + fn items(&self) -> Option>; fn len(&self) -> Option; fn is_empty(&self) -> Option; fn get_key<'a>(&'a self, key: &str) -> Option<&'a Self>; diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index 92fbef710..77dcbbcbf 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -95,7 +95,7 @@ pub fn json_get(manager: M, ctx: &Context, args: Vec) - let key = args.next_arg()?; // Set Capacity to 1 assuming the common case has one path - let mut paths: Vec = Vec::with_capacity(1); + let mut paths = Vec::with_capacity(1); let mut format_options = ReplyFormatOptions::new(is_resp3(ctx), ReplyFormat::STRING); @@ -628,17 +628,15 @@ fn json_type_impl(redis_key: &M::ReadHolder, path: &str) -> RedisResult where M: Manager, { - let root = redis_key.get_value()?; - let value = match root { - Some(root) => KeyValue::new(root) + redis_key.get_value()?.map_or(Ok(RedisValue::Null), |root| { + let value = KeyValue::new(root) .get_values(path)? .into_iter() .map(|v| RedisValue::from(KeyValue::value_name(v))) .collect_vec() - .into(), - None => RedisValue::Null, - }; - Ok(value) + .into(); + Ok(value) + }) } fn json_type_legacy(redis_key: &M::ReadHolder, path: &str) -> RedisResult @@ -1572,14 +1570,12 @@ where err_msg_json_path_doesnt_exist_with_param_or(path, "not an array"), )) } else { - paths + let res = paths .into_iter() - .try_fold(0, |_, p| redis_key.arr_trim(p, start, stop)) - .and_then(|res| { - redis_key.notify_keyspace_event(ctx, "json.arrtrim")?; - manager.apply_changes(ctx); - Ok(res.into()) - }) + .try_fold(0, |_, p| redis_key.arr_trim(p, start, stop))?; + redis_key.notify_keyspace_event(ctx, "json.arrtrim")?; + manager.apply_changes(ctx); + Ok(res.into()) } } @@ -1711,16 +1707,14 @@ pub fn json_clear(manager: M, ctx: &Context, args: Vec) SelectValueType::Double => v.get_double() != 0.0, _ => false, })?; - paths + let cleared = paths .into_iter() - .try_fold(0, |acc, p| redis_key.clear(p).map(|cleared| acc + cleared)) - .and_then(|cleared| { - if cleared > 0 { - redis_key.notify_keyspace_event(ctx, "json.clear")?; - manager.apply_changes(ctx); - } - Ok(cleared.into()) - }) + .try_fold(0, |acc, p| redis_key.clear(p).map(|cleared| acc + cleared))?; + if cleared > 0 { + redis_key.notify_keyspace_event(ctx, "json.clear")?; + manager.apply_changes(ctx); + } + Ok(cleared.into()) } /// diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index eb68bbba4..b2fe9c0de 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -7,7 +7,7 @@ use crate::error::Error; use crate::manager::{err_json, err_msg_json_expected, err_msg_json_path_doesnt_exist}; use crate::manager::{Manager, ReadHolder, WriteHolder}; -use crate::redisjson::normalize_arr_start_index; +use crate::redisjson::{normalize_arr_start_index, ResultInto}; use crate::Format; use crate::REDIS_JSON_TYPE; use bson::{from_document, Document}; @@ -108,11 +108,10 @@ fn replace Result, Error>>( /// If the returned value from `func` is [`None`], the current value is removed. /// If the returned value from `func` is [`Err`], the current value remains (although it could be modified by `func`) /// -fn update Result, Error>>( - path: &[String], - root: &mut IValue, - mut func: F, -) -> Result<(), Error> { +fn update(path: &[String], root: &mut IValue, mut func: F) -> Result, Error> +where + F: FnMut(&mut IValue) -> Result, Error>, +{ let mut target = root; let last_index = path.len().saturating_sub(1); @@ -123,13 +122,16 @@ fn update Result, Error>>( ValueType::Object => { let obj = target_once.as_object_mut().unwrap(); if is_last { - if let Entry::Occupied(mut e) = obj.entry(token) { + let res = if let Entry::Occupied(mut e) = obj.entry(token) { let v = e.get_mut(); - if func(v)?.is_none() { + func(v)?.or_else(|| { e.remove(); - } - } - return Ok(()); + None + }) + } else { + None + }; + return Ok(res); } obj.get_mut(token.as_str()) } @@ -140,13 +142,16 @@ fn update Result, Error>>( arr, token )); if is_last { - if idx < arr.len() { + let res = if idx < arr.len() { let v = &mut arr.as_mut_slice()[idx]; - if func(v)?.is_none() { + func(v)?.or_else(|| { arr.remove(idx); - } - } - return Ok(()); + None + }) + } else { + None + }; + return Ok(res); } arr.get_mut(idx) } @@ -160,27 +165,28 @@ fn update Result, Error>>( } } - Ok(()) + Ok(None) } impl<'a> IValueKeyHolderWrite<'a> { - fn do_op(&mut self, paths: &[String], mut op_fun: F) -> Result<(), RedisError> + fn do_op(&mut self, paths: &[String], mut op_fun: F) -> Result, RedisError> where - F: FnMut(&mut IValue) -> Result, Error>, + F: FnMut(&mut IValue) -> Result, Error>, { let root = self.get_value().unwrap().unwrap(); if paths.is_empty() { // updating the root require special treatment op_fun(root) - .map_err(|err| RedisError::String(err.msg))? - .unwrap_or_else(|| { - root.take(); - }); + .map(|r| { + r.or_else(|| { + root.take(); + None + }) + }) + .map_err(|err| RedisError::String(err.msg)) } else { - update(paths, root, op_fun)?; + update(paths, root, op_fun).into_both() } - - Ok(()) } fn do_num_op( @@ -196,8 +202,7 @@ impl<'a> IValueKeyHolderWrite<'a> { { let in_value = &serde_json::from_str(num)?; if let serde_json::Value::Number(in_value) = in_value { - let mut res = None; - self.do_op(&path, |v| { + let res = self.do_op(&path, |v| { let num_res = match (v.get_type(), in_value.as_i64()) { (SelectValueType::Long, Some(num2)) => { let num1 = v.get_long(); @@ -213,8 +218,7 @@ impl<'a> IValueKeyHolderWrite<'a> { }; let new_val = IValue::from(num_res?); *v = new_val.clone(); - res = Some(new_val); - Ok(Some(())) + Ok(Some(new_val)) })?; match res { None => Err(RedisError::String(err_msg_json_path_doesnt_exist())), @@ -348,7 +352,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { let mut deleted = false; update(&path, self.get_value().unwrap().unwrap(), |_v| { deleted = true; // might delete more than a single value - Ok(None) + Ok(None::<()>) })?; Ok(deleted) } @@ -366,15 +370,15 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn bool_toggle(&mut self, path: Vec) -> Result { - let mut res = None; - self.do_op(&path, |v| { + let res = self.do_op(&path, |v| { if let DestructuredMut::Bool(mut bool_mut) = v.destructure_mut() { //Using DestructuredMut in order to modify a `Bool` variant let val = bool_mut.get() ^ true; bool_mut.set(val); - res = Some(val); + Ok(Some(val)) + } else { + Ok(None) } - Ok(Some(())) })?; res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) } @@ -382,13 +386,11 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { fn str_append(&mut self, path: Vec, val: String) -> Result { let json = serde_json::from_str(&val)?; if let serde_json::Value::String(s) = json { - let mut res = None; - self.do_op(&path, |v| { + let res = self.do_op(&path, |v| { let v_str = v.as_string_mut().unwrap(); let new_str = [v_str.as_str(), s.as_str()].concat(); - res = Some(new_str.len()); *v_str = IString::intern(&new_str); - Ok(Some(())) + Ok(Some(new_str.len())) })?; res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) } else { @@ -400,14 +402,12 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn arr_append(&mut self, path: Vec, args: Vec) -> Result { - let mut res = None; - self.do_op(&path, |v| { + let res = self.do_op(&path, |v| { let arr = v.as_array_mut().unwrap(); for a in &args { arr.push(a.clone()); } - res = Some(arr.len()); - Ok(Some(())) + Ok(Some(arr.len())) })?; res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) } @@ -418,8 +418,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { args: &[IValue], index: i64, ) -> Result { - let mut res = None; - self.do_op(&paths, |v: &mut IValue| { + let res = self.do_op(&paths, |v: &mut IValue| { // Verify legal index in bounds let len = v.len().unwrap() as i64; let index = if index < 0 { len + index } else { index }; @@ -433,8 +432,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { curr.insert(index, a.clone()); index += 1; } - res = Some(curr.len()); - Ok(Some(())) + Ok(Some(curr.len())) })?; res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) } @@ -464,8 +462,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn arr_trim(&mut self, path: Vec, start: i64, stop: i64) -> Result { - let mut res = None; - self.do_op(&path, |v| { + let res = self.do_op(&path, |v| { if let Some(array) = v.as_array_mut() { let len = array.len() as i64; let stop = stop.normalize(len); @@ -482,8 +479,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { array.rotate_left(range.start); array.truncate(range.end - range.start); - res = Some(array.len()); - Ok(Some(())) + Ok(Some(array.len())) } else { Err(err_json(v, "array")) } @@ -492,27 +488,25 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn clear(&mut self, path: Vec) -> Result { - let mut cleared = 0; - self.do_op(&path, |v| match v.type_() { - ValueType::Object => { - let obj = v.as_object_mut().unwrap(); - obj.clear(); - cleared += 1; - Ok(Some(())) - } - ValueType::Array => { - let arr = v.as_array_mut().unwrap(); - arr.clear(); - cleared += 1; - Ok(Some(())) - } - ValueType::Number => { - *v = IValue::from(0); - cleared += 1; - Ok(Some(())) - } - _ => Ok(Some(())), - })?; + let cleared = self + .do_op(&path, |v| match v.type_() { + ValueType::Object => { + let obj = v.as_object_mut().unwrap(); + obj.clear(); + Ok(Some(1)) + } + ValueType::Array => { + let arr = v.as_array_mut().unwrap(); + arr.clear(); + Ok(Some(1)) + } + ValueType::Number => { + *v = IValue::from(0); + Ok(Some(1)) + } + _ => Ok(None), + })? + .unwrap_or(0); Ok(cleared) } } diff --git a/redis_json/src/key_value.rs b/redis_json/src/key_value.rs index b58df53f5..b451048b9 100644 --- a/redis_json/src/key_value.rs +++ b/redis_json/src/key_value.rs @@ -241,7 +241,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { paths: Vec, format: ReplyFormatOptions, ) -> Result { - let is_legacy = !paths.iter().any(|p| !p.is_legacy()); + let is_legacy = paths.iter().all(Path::is_legacy); // If we're using RESP3, we need to reply with an array of values if format.is_resp3_reply() { diff --git a/test.out b/test.out new file mode 100644 index 000000000..6fe10a6ca --- /dev/null +++ b/test.out @@ -0,0 +1,526 @@ +929534:C 12 Aug 2024 17:51:28.325 # WARNING: Changing databases number from 16 to 1 since we are in cluster mode +929534:C 12 Aug 2024 17:51:28.325 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect. +929534:C 12 Aug 2024 17:51:28.325 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo +929534:C 12 Aug 2024 17:51:28.325 * Redis version=7.2.3, bits=64, commit=7f4bae81, modified=0, pid=929534, just started +929534:C 12 Aug 2024 17:51:28.325 * Configuration loaded +929534:M 12 Aug 2024 17:51:28.326 * monotonic clock: POSIX clock_gettime +929534:M 12 Aug 2024 17:51:28.327 * Running mode=cluster, port=6379. +929534:M 12 Aug 2024 17:51:28.333 * No cluster configuration found, I'm 75dbf2b0fbe9a363151daa6280bb9679b5a845d3 +929534:M 12 Aug 2024 17:51:28.336 * Created new data type 'ReJSON-RL' +929534:M 12 Aug 2024 17:51:28.336 * version: 999999 git sha: unknown branch: unknown +929534:M 12 Aug 2024 17:51:28.336 * Exported RedisJSON_V1 API +929534:M 12 Aug 2024 17:51:28.336 * Exported RedisJSON_V2 API +929534:M 12 Aug 2024 17:51:28.336 * Exported RedisJSON_V3 API +929534:M 12 Aug 2024 17:51:28.336 * Exported RedisJSON_V4 API +929534:M 12 Aug 2024 17:51:28.336 * Exported RedisJSON_V5 API +929534:M 12 Aug 2024 17:51:28.336 * Enabled diskless replication +929534:M 12 Aug 2024 17:51:28.336 * Module 'ReJSON' loaded from /home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so +929534:M 12 Aug 2024 17:51:28.336 * Server initialized +929534:M 12 Aug 2024 17:51:28.336 * Ready to accept connections tcp +929534:M 12 Aug 2024 17:51:28.437 * IP address for this node updated to 127.0.0.1 +929534:M 12 Aug 2024 17:51:30.353 * Cluster state changed: ok +thread '' panicked at redis_json/src/key_value.rs:182:9: +Path: $..[?@>0], Format: ReplyFormatOptions { format: STRING, indent: None, space: None, newline: None, resp3: false }, is_legacy: false +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +fatal runtime error: failed to initiate panic, error 5 + + +=== REDIS BUG REPORT START: Cut & paste starting from here === +929534:M 12 Aug 2024 17:51:30.382 # Redis 7.2.3 crashed by signal: 6, si_code: -6 +929534:M 12 Aug 2024 17:51:30.382 # Crashed running the instruction at: 0x7fca65c239fc + +------ STACK TRACE ------ +EIP: +/lib/x86_64-linux-gnu/libc.so.6(pthread_kill+0x12c)[0x7fca65c239fc] + +Backtrace: +/lib/x86_64-linux-gnu/libc.so.6(+0x42520)[0x7fca65bcf520] +/lib/x86_64-linux-gnu/libc.so.6(pthread_kill+0x12c)[0x7fca65c239fc] +/lib/x86_64-linux-gnu/libc.so.6(raise+0x16)[0x7fca65bcf476] +/lib/x86_64-linux-gnu/libc.so.6(abort+0xd3)[0x7fca65bb57f3] +/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0x299aba)[0x7fca656efaba] +/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0x297faf)[0x7fca656edfaf] +/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0x297d2d)[0x7fca656edd2d] +/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0x297a94)[0x7fca656eda94] +/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0x296d89)[0x7fca656ecd89] +/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0x2977c7)[0x7fca656ed7c7] +/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0x5f933)[0x7fca654b5933] +/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0xff485)[0x7fca65555485] +/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0xc14f0)[0x7fca655174f0] +/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0x1126e5)[0x7fca655686e5] +redis-server *:6379 [cluster](RedisModuleCommandDispatcher+0xa8)[0x560f0fb65be8] +redis-server *:6379 [cluster](call+0x169)[0x560f0fa7d989] +redis-server *:6379 [cluster](processCommand+0xdaf)[0x560f0fa7f69f] +redis-server *:6379 [cluster](processInputBuffer+0x158)[0x560f0fa98b78] +redis-server *:6379 [cluster](readQueryFromClient+0x58e)[0x560f0fa9819e] +redis-server *:6379 [cluster](+0x1c24be)[0x560f0fbbb4be] +redis-server *:6379 [cluster](aeProcessEvents+0x331)[0x560f0fa6d041] +redis-server *:6379 [cluster](main+0xc6d)[0x560f0fa89c1d] +/lib/x86_64-linux-gnu/libc.so.6(+0x29d90)[0x7fca65bb6d90] +/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80)[0x7fca65bb6e40] +redis-server *:6379 [cluster](_start+0x25)[0x560f0fa681d5] + +------ REGISTERS ------ +929534:M 12 Aug 2024 17:51:30.383 # +RAX:0000000000000000 RBX:00007fca65b8c180 +RCX:00007fca65c239fc RDX:0000000000000006 +RDI:00000000000e2efe RSI:00000000000e2efe +RBP:00000000000e2efe RSP:00007ffc4f102f60 +R8 :00007ffc4f103030 R9 :0000000000000000 +R10:0000000000000008 R11:0000000000000246 +R12:0000000000000006 R13:0000000000000016 +R14:00007fca65820280 R15:0000560f1045dc40 +RIP:00007fca65c239fc EFL:0000000000000246 +CSGSFS:002b000000000033 +929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f6f) -> 0000000000000000 +929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f6e) -> 00007ffc4f103bf8 +929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f6d) -> 0000000000000000 +929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f6c) -> 00007ffc4f103be8 +929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f6b) -> 00007ffc4f103be0 +929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f6a) -> 00007ffc4f103bd8 +929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f69) -> 00007ffc4f103bd0 +929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f68) -> 00007ffc4f103bc8 +929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f67) -> 0000000000000000 +929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f66) -> 0000000000000000 +929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f65) -> 0000000000000000 +929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f64) -> 0000000000000000 +929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f63) -> 0000000000000000 +929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f62) -> 00007ffc4f103bc0 +929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f61) -> 0000000000000000 +929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f60) -> 0000000000000000 + +------ INFO OUTPUT ------ +# Server +redis_version:7.2.3 +redis_git_sha1:7f4bae81 +redis_git_dirty:0 +redis_build_id:aafa0da75adb3896 +redis_mode:cluster +os:Linux 5.14.0-1054-oem x86_64 +arch_bits:64 +monotonic_clock:POSIX clock_gettime +multiplexing_api:epoll +atomicvar_api:c11-builtin +gcc_version:4.2.1 +process_id:929534 +process_supervised:no +run_id:19dbe36dcd88d3c58cc70ed2f978ff2fb7722b58 +tcp_port:6379 +server_time_usec:1723474290382665 +uptime_in_seconds:2 +uptime_in_days:0 +hz:10 +configured_hz:10 +lru_clock:12198258 +executable:/home/ephraim_feldblum/git/RedisJSON/logs/redis-server +config_file: +io_threads_active:0 +listener0:name=tcp,bind=*,bind=-::*,port=6379 + +# Clients +connected_clients:1 +cluster_connections:0 +maxclients:10000 +client_recent_max_input_buffer:32792 +client_recent_max_output_buffer:0 +blocked_clients:0 +tracking_clients:0 +clients_in_timeout_table:0 +total_blocking_keys:0 +total_blocking_keys_on_nokey:0 + +# Memory +used_memory:1903024 +used_memory_human:1.81M +used_memory_rss:11616256 +used_memory_rss_human:11.08M +used_memory_peak:2497864 +used_memory_peak_human:2.38M +used_memory_peak_perc:76.19% +used_memory_overhead:1693384 +used_memory_startup:1693072 +used_memory_dataset:209640 +used_memory_dataset_perc:99.85% +allocator_allocated:1877704 +allocator_active:11584512 +allocator_resident:11584512 +total_system_memory:33378541568 +total_system_memory_human:31.09G +used_memory_lua:31744 +used_memory_vm_eval:31744 +used_memory_lua_human:31.00K +used_memory_scripts_eval:0 +number_of_cached_scripts:0 +number_of_functions:0 +number_of_libraries:0 +used_memory_vm_functions:32768 +used_memory_vm_total:64512 +used_memory_vm_total_human:63.00K +used_memory_functions:216 +used_memory_scripts:216 +used_memory_scripts_human:216B +maxmemory:0 +maxmemory_human:0B +maxmemory_policy:noeviction +allocator_frag_ratio:6.17 +allocator_frag_bytes:9706808 +allocator_rss_ratio:1.00 +allocator_rss_bytes:0 +rss_overhead_ratio:1.00 +rss_overhead_bytes:31744 +mem_fragmentation_ratio:6.19 +mem_fragmentation_bytes:9738552 +mem_not_counted_for_evict:0 +mem_replication_backlog:0 +mem_total_replication_buffers:0 +mem_clients_slaves:0 +mem_clients_normal:0 +mem_cluster_links:0 +mem_aof_buffer:0 +mem_allocator:libc +active_defrag_running:0 +lazyfree_pending_objects:0 +lazyfreed_objects:0 + +# Persistence +loading:0 +async_loading:0 +current_cow_peak:0 +current_cow_size:0 +current_cow_size_age:0 +current_fork_perc:0.00 +current_save_keys_processed:0 +current_save_keys_total:0 +rdb_changes_since_last_save:3 +rdb_bgsave_in_progress:0 +rdb_last_save_time:1723474288 +rdb_last_bgsave_status:ok +rdb_last_bgsave_time_sec:-1 +rdb_current_bgsave_time_sec:-1 +rdb_saves:0 +rdb_last_cow_size:0 +rdb_last_load_keys_expired:0 +rdb_last_load_keys_loaded:0 +aof_enabled:0 +aof_rewrite_in_progress:0 +aof_rewrite_scheduled:0 +aof_last_rewrite_time_sec:-1 +aof_current_rewrite_time_sec:-1 +aof_last_bgrewrite_status:ok +aof_rewrites:0 +aof_rewrites_consecutive_failures:0 +aof_last_write_status:ok +aof_last_cow_size:0 +module_fork_in_progress:0 +module_fork_last_cow_size:0 + +# Stats +total_connections_received:25 +total_commands_processed:77 +instantaneous_ops_per_sec:29 +total_net_input_bytes:172775 +total_net_output_bytes:10686 +total_net_repl_input_bytes:0 +total_net_repl_output_bytes:0 +instantaneous_input_kbps:1.33 +instantaneous_output_kbps:4.69 +instantaneous_input_repl_kbps:0.00 +instantaneous_output_repl_kbps:0.00 +rejected_connections:0 +sync_full:0 +sync_partial_ok:0 +sync_partial_err:0 +expired_keys:0 +expired_stale_perc:0.00 +expired_time_cap_reached_count:0 +expire_cycle_cpu_milliseconds:0 +evicted_keys:0 +evicted_clients:0 +total_eviction_exceeded_time:0 +current_eviction_exceeded_time:0 +keyspace_hits:1 +keyspace_misses:0 +pubsub_channels:0 +pubsub_patterns:0 +pubsubshard_channels:0 +latest_fork_usec:0 +total_forks:0 +migrate_cached_sockets:0 +slave_expires_tracked_keys:0 +active_defrag_hits:0 +active_defrag_misses:0 +active_defrag_key_hits:0 +active_defrag_key_misses:0 +total_active_defrag_time:0 +current_active_defrag_time:0 +tracking_total_keys:0 +tracking_total_items:0 +tracking_total_prefixes:0 +unexpected_error_replies:0 +total_error_replies:2 +dump_payload_sanitizations:0 +total_reads_processed:111 +total_writes_processed:79 +io_threaded_reads_processed:0 +io_threaded_writes_processed:0 +reply_buffer_shrinks:1 +reply_buffer_expands:0 +eventloop_cycles:161 +eventloop_duration_sum:13216 +eventloop_duration_cmd_sum:1423 +instantaneous_eventloop_cycles_per_sec:59 +instantaneous_eventloop_duration_usec:82 +acl_access_denied_auth:0 +acl_access_denied_cmd:0 +acl_access_denied_key:0 +acl_access_denied_channel:0 + +# Replication +role:master +connected_slaves:0 +master_failover_state:no-failover +master_replid:470fb44f23bf3f61b2af16d17bc14e5fa542c5b2 +master_replid2:0000000000000000000000000000000000000000 +master_repl_offset:0 +second_repl_offset:-1 +repl_backlog_active:0 +repl_backlog_size:1048576 +repl_backlog_first_byte_offset:0 +repl_backlog_histlen:0 + +# CPU +used_cpu_sys:0.021820 +used_cpu_user:0.005455 +used_cpu_sys_children:0.000000 +used_cpu_user_children:0.000000 +used_cpu_sys_main_thread:0.021775 +used_cpu_user_main_thread:0.005443 + +# Modules +module:name=ReJSON,ver=999999,api=1,filters=0,usedby=[],using=[],options=[handle-io-errors] + +# Commandstats +cmdstat_json.set:calls=3,usec=67,usec_per_call=22.33,rejected_calls=0,failed_calls=0 +cmdstat_client|setinfo:calls=50,usec=57,usec_per_call=1.14,rejected_calls=0,failed_calls=0 +cmdstat_info:calls=1,usec=27,usec_per_call=27.00,rejected_calls=0,failed_calls=0 +cmdstat_cluster|addslots:calls=1,usec=308,usec_per_call=308.00,rejected_calls=0,failed_calls=0 +cmdstat_cluster|info:calls=20,usec=921,usec_per_call=46.05,rejected_calls=0,failed_calls=0 +cmdstat_cluster|meet:calls=1,usec=43,usec_per_call=43.00,rejected_calls=0,failed_calls=0 +cmdstat_ping:calls=1,usec=0,usec_per_call=0.00,rejected_calls=0,failed_calls=0 + +# Errorstats +errorstat_ERR:count=2 + +# Latencystats +latency_percentiles_usec_json.set:p50=28.031,p99=38.143,p99.9=38.143 +latency_percentiles_usec_client|setinfo:p50=1.003,p99=5.023,p99.9=5.023 +latency_percentiles_usec_info:p50=27.007,p99=27.007,p99.9=27.007 +latency_percentiles_usec_cluster|addslots:p50=309.247,p99=309.247,p99.9=309.247 +latency_percentiles_usec_cluster|info:p50=46.079,p99=99.327,p99.9=99.327 +latency_percentiles_usec_cluster|meet:p50=43.007,p99=43.007,p99.9=43.007 +latency_percentiles_usec_ping:p50=0.001,p99=0.001,p99.9=0.001 + +# Cluster +cluster_enabled:1 + +# Keyspace +db0:keys=1,expires=0,avg_ttl=0 + +# Cluster info +cluster_state:ok +cluster_slots_assigned:16384 +cluster_slots_ok:16384 +cluster_slots_pfail:0 +cluster_slots_fail:0 +cluster_known_nodes:1 +cluster_size:1 +cluster_current_epoch:0 +cluster_my_epoch:0 +cluster_stats_messages_pong_sent:1 +cluster_stats_messages_meet_sent:1 +cluster_stats_messages_sent:2 +cluster_stats_messages_pong_received:1 +cluster_stats_messages_meet_received:1 +cluster_stats_messages_received:2 +total_cluster_links_buffer_limit_exceeded:0 + +------ CLUSTER NODES OUTPUT ------ +75dbf2b0fbe9a363151daa6280bb9679b5a845d3 127.0.0.1:6379@16379,,tls-port=0,shard-id=28351096806fb67929f04318dace2f99aaab441e myself,master - 0 0 0 connected 0-16383 + +------ CLIENT LIST OUTPUT ------ +id=30 addr=127.0.0.1:38544 laddr=127.0.0.1:6379 fd=11 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=40 qbuf-free=16346 argv-mem=18 multi-mem=0 rbs=16392 rbp=16392 obl=0 oll=0 omem=0 tot-mem=33602 events=r cmd=json.get user=default redir=-1 resp=2 lib-name=redis-py lib-ver=5.0.7 + +------ CURRENT CLIENT INFO ------ +id=30 addr=127.0.0.1:38544 laddr=127.0.0.1:6379 fd=11 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=40 qbuf-free=16346 argv-mem=18 multi-mem=0 rbs=16392 rbp=16392 obl=0 oll=0 omem=0 tot-mem=33602 events=r cmd=json.get user=default redir=-1 resp=2 lib-name=redis-py lib-ver=5.0.7 +argc: '3' +argv[0]: '"JSON.GET"' +argv[1]: '"k"' +argv[2]: '"$..[?@>0]"' +929534:M 12 Aug 2024 17:51:30.383 # key 'k' found in DB containing the following object: +929534:M 12 Aug 2024 17:51:30.383 # Object type: 5 +929534:M 12 Aug 2024 17:51:30.383 # Object encoding: 0 +929534:M 12 Aug 2024 17:51:30.383 # Object refcount: 1 + +------ EXECUTING CLIENT INFO ------ +id=30 addr=127.0.0.1:38544 laddr=127.0.0.1:6379 fd=11 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=40 qbuf-free=16346 argv-mem=18 multi-mem=0 rbs=16392 rbp=16392 obl=0 oll=0 omem=0 tot-mem=33602 events=r cmd=json.get user=default redir=-1 resp=2 lib-name=redis-py lib-ver=5.0.7 +argc: '3' +argv[0]: '"JSON.GET"' +argv[1]: '"k"' +argv[2]: '"$..[?@>0]"' +929534:M 12 Aug 2024 17:51:30.383 # key 'k' found in DB containing the following object: +929534:M 12 Aug 2024 17:51:30.383 # Object type: 5 +929534:M 12 Aug 2024 17:51:30.383 # Object encoding: 0 +929534:M 12 Aug 2024 17:51:30.383 # Object refcount: 1 + +------ MODULES INFO OUTPUT ------ +# ReJSON_trace +ReJSON_backtrace: 0: redis_module::add_trace_info + at /home/ephraim_feldblum/.cargo/registry/src/index.crates.io-6f17d22bba15001f/redis-module-2.0.7/src/lib.rs:70:29 + redis_module::basic_info_command_handler + at /home/ephraim_feldblum/.cargo/registry/src/index.crates.io-6f17d22bba15001f/redis-module-2.0.7/src/lib.rs:94:25 + 1: rejson::__info_func + at /home/ephraim_feldblum/.cargo/registry/src/index.crates.io-6f17d22bba15001f/redis-module-2.0.7/src/macros.rs:174:13 + 2: modulesCollectInfo + at /home/ephraim_feldblum/git/redis/src/module.c:10293:9 + 3: logModulesInfo + at /home/ephraim_feldblum/git/redis/src/debug.c:1900:22 + printCrashReport + at /home/ephraim_feldblum/git/redis/src/debug.c:2183:5 + 4: sigsegvHandler + at /home/ephraim_feldblum/git/redis/src/debug.c:2164:5 + 5: + 6: __pthread_kill_implementation + at ./nptl/pthread_kill.c:43:17 + __pthread_kill_internal + at ./nptl/pthread_kill.c:78:10 + __GI___pthread_kill + at ./nptl/pthread_kill.c:89:10 + 7: __GI_raise + at ./signal/../sysdeps/posix/raise.c:26:13 + 8: __GI_abort + at ./stdlib/abort.c:79:7 + 9: std::sys::pal::unix::abort_internal + at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/sys/pal/unix/mod.rs:370:14 + 10: rust_panic + at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/rt.rs:43:13 + 11: std::panicking::rust_panic_with_hook + at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/panicking.rs:817:5 + 12: std::panicking::begin_panic_handler::{{closure}} + at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/panicking.rs:664:13 + 13: std::sys_common::backtrace::__rust_end_short_backtrace + at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/sys_common/backtrace.rs:171:18 + 14: rust_begin_unwind + at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/panicking.rs:652:5 + 15: core::panicking::panic_fmt + at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/core/src/panicking.rs:72:14 + 16: rejson::key_value::KeyValue::to_json_single + at /home/ephraim_feldblum/git/RedisJSON/redis_json/src/key_value.rs:182:9 + rejson::key_value::KeyValue::to_json + at /home/ephraim_feldblum/git/RedisJSON/redis_json/src/key_value.rs:253:13 + 17: rejson::commands::json_get + at /home/ephraim_feldblum/git/RedisJSON/redis_json/src/commands.rs:142:22 + 18: rejson::RedisModule_OnLoad::__do_command::{{closure}}::{{closure}} + at /home/ephraim_feldblum/git/RedisJSON/redis_json/src/lib.rs:139:36 + rejson::RedisModule_OnLoad::__do_command::{{closure}} + at /home/ephraim_feldblum/git/RedisJSON/redis_json/src/lib.rs:97:27 + rejson::RedisModule_OnLoad::__do_command + at /home/ephraim_feldblum/.cargo/registry/src/index.crates.io-6f17d22bba15001f/redis-module-2.0.7/src/macros.rs:22:28 + 19: RedisModuleCommandDispatcher + at /home/ephraim_feldblum/git/redis/src/module.c:917:5 + 20: call + at /home/ephraim_feldblum/git/redis/src/server.c:3519:5 + 21: processCommand + at /home/ephraim_feldblum/git/redis/src/server.c:4160:9 + 22: processCommandAndResetClient + at /home/ephraim_feldblum/git/redis/src/networking.c:2466:9 + processInputBuffer + at /home/ephraim_feldblum/git/redis/src/networking.c:2574:17 + 23: readQueryFromClient + at /home/ephraim_feldblum/git/redis/src/networking.c:2713:9 + 24: callHandler + at /home/ephraim_feldblum/git/redis/src/./connhelpers.h:79:18 + connSocketEventHandler + at /home/ephraim_feldblum/git/redis/src/socket.c:298:14 + 25: aeProcessEvents + at /home/ephraim_feldblum/git/redis/src/ae.c:436:17 + 26: aeMain + at /home/ephraim_feldblum/git/redis/src/ae.c:496:9 + main + at /home/ephraim_feldblum/git/redis/src/server.c:7360:5 + 27: __libc_start_call_main + at ./csu/../sysdeps/nptl/libc_start_call_main.h:58:16 + 28: __libc_start_main_impl + at ./csu/../csu/libc-start.c:392:3 + 29: _start + + +------ CONFIG DEBUG OUTPUT ------ +lazyfree-lazy-server-del no +sanitize-dump-payload no +io-threads-do-reads no +list-compress-depth 0 +repl-diskless-load disabled +replica-read-only yes +repl-diskless-sync yes +client-query-buffer-limit 1gb +lazyfree-lazy-expire no +slave-read-only yes +io-threads 1 +lazyfree-lazy-eviction no +proto-max-bulk-len 512mb +activedefrag no +lazyfree-lazy-user-del no +lazyfree-lazy-user-flush no + +------ FAST MEMORY TEST ------ +929534:M 12 Aug 2024 17:51:30.458 # Bio worker thread #0 terminated +929534:M 12 Aug 2024 17:51:30.458 # Bio worker thread #1 terminated +929534:M 12 Aug 2024 17:51:30.458 # Bio worker thread #2 terminated +*** Preparing to test memory region 560f0fcbf000 (135168 bytes) +*** Preparing to test memory region 560f1036b000 (49389568 bytes) +*** Preparing to test memory region 7fca60d47000 (6115328 bytes) +*** Preparing to test memory region 7fca61ec0000 (1110016 bytes) +*** Preparing to test memory region 7fca63710000 (3047424 bytes) +*** Preparing to test memory region 7fca63c18000 (8388608 bytes) +*** Preparing to test memory region 7fca64419000 (8388608 bytes) +*** Preparing to test memory region 7fca64c1a000 (8388608 bytes) +*** Preparing to test memory region 7fca65821000 (991232 bytes) +*** Preparing to test memory region 7fca65b8b000 (8192 bytes) +*** Preparing to test memory region 7fca65da9000 (53248 bytes) +*** Preparing to test memory region 7fca661f7000 (12288 bytes) +*** Preparing to test memory region 7fca6639d000 (8192 bytes) +.O.O.O.O.O.O.O.O.O.O.O.O.O +Fast memory test PASSED, however your memory can still be broken. Please run a memory test for several hours if possible. + +------ DUMPING CODE AROUND EIP ------ +Symbol: pthread_kill (base: 0x7fca65c238d0) +Module: /lib/x86_64-linux-gnu/libc.so.6 (base 0x7fca65b8d000) +$ xxd -r -p /tmp/dump.hex /tmp/dump.bin +$ objdump --adjust-vma=0x7fca65c238d0 -D -b binary -m i386:x86-64 /tmp/dump.bin +------ +929534:M 12 Aug 2024 17:51:30.584 # dump of function (hexdump of 428 bytes): +f30f1efa41568d56e0415541bd16000000415455534881ec9000000064488b042528000000488984248800000031c083fa01767e4889fb4189f464483b3c25100000000f84c70000004989e641ba0800000031ffb80e0000004c89f2488d352dc113000f0531c0488dab74090000ba01000000f00fb155000f85ca00000080bb730900000074594531ed31d287937409000083fa010f8fbd00000041ba0800000031d24c89f6bf02000000b80e0000000f05488b84248800000064482b0425280000000f859c0000004881c4900000004489e85b5d415c415d415ec30f1f4000448babd0020000e8845605004489e289c74489eeb8ea0000000f053d00f0ffff76854189c541f7ddeb80660f1f440000b8ba0000000f0589c5e8525605004489e289ee89c7b8ea0000000f054189c541f7dd3d00f0ffffb800000000440f46e8e96dffffff0f1f004889efe810a8ffffe929ffffff0f1f004889efe8d0a8ffffe936ffffffe816fb0900660f1f440000f30f1efac3662e0f1f840000000000904157415641554154554889fd534883ec2864488b042528000000488944241831c0648b0425d0020000894424 +Function at 0x7fca65c79040 is getpid +Function at 0x7fca65cc3550 is __stack_chk_fail + +=== REDIS BUG REPORT END. Make sure to include from START to END. === + + Please report the crash by opening an issue on github: + + http://github.com/redis/redis/issues + + If a Redis module was involved, please open in the module's repo instead. + + Suspect RAM error? Use redis-server --test-memory to verify it. + + Some other issues could be detected by redis-server --check-system + ❌ (FAIL): 'Connection closed by server.' == '[1]' test.py:1538 +test:test_recursive_descent: + [FAIL] + process is not alive, might have crash durring test execution, check this out. server id : 1 + +Test Took: 2 sec +Total Tests Run: 1, Total Tests Failed: 1, Total Tests Passed: 0 +Failed Tests Summary: + test:test_recursive_descent + ❌ (FAIL): 'Connection closed by server.' == '[1]' test.py:1538 diff --git a/tests/pytest/test.py b/tests/pytest/test.py index 515fcd06a..0f748f9d1 100644 --- a/tests/pytest/test.py +++ b/tests/pytest/test.py @@ -1533,6 +1533,9 @@ def test_recursive_descent(env): r.expect('JSON.SET', 'k', '$', '[{"a":1}]').ok() r.expect('JSON.SET', 'k', '$..*', '[{"a":1}]').ok() r.expect('JSON.GET', 'k', '$').equal('[[[{"a":1}]]]') + + r.expect('JSON.SET', 'k', '$', '[1]').ok() + r.expect('JSON.GET', 'k', '$..[?@>0]').equal('[1]') # class CacheTestCase(BaseReJSONTest): # @property From 2e108b928f282946fd5ee57a1ce8b0965c9c8e54 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Tue, 13 Aug 2024 17:09:15 +0300 Subject: [PATCH 06/33] delete useless test output --- test.out | 526 ------------------------------------------------------- 1 file changed, 526 deletions(-) delete mode 100644 test.out diff --git a/test.out b/test.out deleted file mode 100644 index 6fe10a6ca..000000000 --- a/test.out +++ /dev/null @@ -1,526 +0,0 @@ -929534:C 12 Aug 2024 17:51:28.325 # WARNING: Changing databases number from 16 to 1 since we are in cluster mode -929534:C 12 Aug 2024 17:51:28.325 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect. -929534:C 12 Aug 2024 17:51:28.325 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo -929534:C 12 Aug 2024 17:51:28.325 * Redis version=7.2.3, bits=64, commit=7f4bae81, modified=0, pid=929534, just started -929534:C 12 Aug 2024 17:51:28.325 * Configuration loaded -929534:M 12 Aug 2024 17:51:28.326 * monotonic clock: POSIX clock_gettime -929534:M 12 Aug 2024 17:51:28.327 * Running mode=cluster, port=6379. -929534:M 12 Aug 2024 17:51:28.333 * No cluster configuration found, I'm 75dbf2b0fbe9a363151daa6280bb9679b5a845d3 -929534:M 12 Aug 2024 17:51:28.336 * Created new data type 'ReJSON-RL' -929534:M 12 Aug 2024 17:51:28.336 * version: 999999 git sha: unknown branch: unknown -929534:M 12 Aug 2024 17:51:28.336 * Exported RedisJSON_V1 API -929534:M 12 Aug 2024 17:51:28.336 * Exported RedisJSON_V2 API -929534:M 12 Aug 2024 17:51:28.336 * Exported RedisJSON_V3 API -929534:M 12 Aug 2024 17:51:28.336 * Exported RedisJSON_V4 API -929534:M 12 Aug 2024 17:51:28.336 * Exported RedisJSON_V5 API -929534:M 12 Aug 2024 17:51:28.336 * Enabled diskless replication -929534:M 12 Aug 2024 17:51:28.336 * Module 'ReJSON' loaded from /home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so -929534:M 12 Aug 2024 17:51:28.336 * Server initialized -929534:M 12 Aug 2024 17:51:28.336 * Ready to accept connections tcp -929534:M 12 Aug 2024 17:51:28.437 * IP address for this node updated to 127.0.0.1 -929534:M 12 Aug 2024 17:51:30.353 * Cluster state changed: ok -thread '' panicked at redis_json/src/key_value.rs:182:9: -Path: $..[?@>0], Format: ReplyFormatOptions { format: STRING, indent: None, space: None, newline: None, resp3: false }, is_legacy: false -note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace -fatal runtime error: failed to initiate panic, error 5 - - -=== REDIS BUG REPORT START: Cut & paste starting from here === -929534:M 12 Aug 2024 17:51:30.382 # Redis 7.2.3 crashed by signal: 6, si_code: -6 -929534:M 12 Aug 2024 17:51:30.382 # Crashed running the instruction at: 0x7fca65c239fc - ------- STACK TRACE ------ -EIP: -/lib/x86_64-linux-gnu/libc.so.6(pthread_kill+0x12c)[0x7fca65c239fc] - -Backtrace: -/lib/x86_64-linux-gnu/libc.so.6(+0x42520)[0x7fca65bcf520] -/lib/x86_64-linux-gnu/libc.so.6(pthread_kill+0x12c)[0x7fca65c239fc] -/lib/x86_64-linux-gnu/libc.so.6(raise+0x16)[0x7fca65bcf476] -/lib/x86_64-linux-gnu/libc.so.6(abort+0xd3)[0x7fca65bb57f3] -/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0x299aba)[0x7fca656efaba] -/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0x297faf)[0x7fca656edfaf] -/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0x297d2d)[0x7fca656edd2d] -/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0x297a94)[0x7fca656eda94] -/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0x296d89)[0x7fca656ecd89] -/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0x2977c7)[0x7fca656ed7c7] -/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0x5f933)[0x7fca654b5933] -/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0xff485)[0x7fca65555485] -/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0xc14f0)[0x7fca655174f0] -/home/ephraim_feldblum/git/RedisJSON/bin/linux-x64-release/rejson.so(+0x1126e5)[0x7fca655686e5] -redis-server *:6379 [cluster](RedisModuleCommandDispatcher+0xa8)[0x560f0fb65be8] -redis-server *:6379 [cluster](call+0x169)[0x560f0fa7d989] -redis-server *:6379 [cluster](processCommand+0xdaf)[0x560f0fa7f69f] -redis-server *:6379 [cluster](processInputBuffer+0x158)[0x560f0fa98b78] -redis-server *:6379 [cluster](readQueryFromClient+0x58e)[0x560f0fa9819e] -redis-server *:6379 [cluster](+0x1c24be)[0x560f0fbbb4be] -redis-server *:6379 [cluster](aeProcessEvents+0x331)[0x560f0fa6d041] -redis-server *:6379 [cluster](main+0xc6d)[0x560f0fa89c1d] -/lib/x86_64-linux-gnu/libc.so.6(+0x29d90)[0x7fca65bb6d90] -/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80)[0x7fca65bb6e40] -redis-server *:6379 [cluster](_start+0x25)[0x560f0fa681d5] - ------- REGISTERS ------ -929534:M 12 Aug 2024 17:51:30.383 # -RAX:0000000000000000 RBX:00007fca65b8c180 -RCX:00007fca65c239fc RDX:0000000000000006 -RDI:00000000000e2efe RSI:00000000000e2efe -RBP:00000000000e2efe RSP:00007ffc4f102f60 -R8 :00007ffc4f103030 R9 :0000000000000000 -R10:0000000000000008 R11:0000000000000246 -R12:0000000000000006 R13:0000000000000016 -R14:00007fca65820280 R15:0000560f1045dc40 -RIP:00007fca65c239fc EFL:0000000000000246 -CSGSFS:002b000000000033 -929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f6f) -> 0000000000000000 -929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f6e) -> 00007ffc4f103bf8 -929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f6d) -> 0000000000000000 -929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f6c) -> 00007ffc4f103be8 -929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f6b) -> 00007ffc4f103be0 -929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f6a) -> 00007ffc4f103bd8 -929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f69) -> 00007ffc4f103bd0 -929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f68) -> 00007ffc4f103bc8 -929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f67) -> 0000000000000000 -929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f66) -> 0000000000000000 -929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f65) -> 0000000000000000 -929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f64) -> 0000000000000000 -929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f63) -> 0000000000000000 -929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f62) -> 00007ffc4f103bc0 -929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f61) -> 0000000000000000 -929534:M 12 Aug 2024 17:51:30.383 # (00007ffc4f102f60) -> 0000000000000000 - ------- INFO OUTPUT ------ -# Server -redis_version:7.2.3 -redis_git_sha1:7f4bae81 -redis_git_dirty:0 -redis_build_id:aafa0da75adb3896 -redis_mode:cluster -os:Linux 5.14.0-1054-oem x86_64 -arch_bits:64 -monotonic_clock:POSIX clock_gettime -multiplexing_api:epoll -atomicvar_api:c11-builtin -gcc_version:4.2.1 -process_id:929534 -process_supervised:no -run_id:19dbe36dcd88d3c58cc70ed2f978ff2fb7722b58 -tcp_port:6379 -server_time_usec:1723474290382665 -uptime_in_seconds:2 -uptime_in_days:0 -hz:10 -configured_hz:10 -lru_clock:12198258 -executable:/home/ephraim_feldblum/git/RedisJSON/logs/redis-server -config_file: -io_threads_active:0 -listener0:name=tcp,bind=*,bind=-::*,port=6379 - -# Clients -connected_clients:1 -cluster_connections:0 -maxclients:10000 -client_recent_max_input_buffer:32792 -client_recent_max_output_buffer:0 -blocked_clients:0 -tracking_clients:0 -clients_in_timeout_table:0 -total_blocking_keys:0 -total_blocking_keys_on_nokey:0 - -# Memory -used_memory:1903024 -used_memory_human:1.81M -used_memory_rss:11616256 -used_memory_rss_human:11.08M -used_memory_peak:2497864 -used_memory_peak_human:2.38M -used_memory_peak_perc:76.19% -used_memory_overhead:1693384 -used_memory_startup:1693072 -used_memory_dataset:209640 -used_memory_dataset_perc:99.85% -allocator_allocated:1877704 -allocator_active:11584512 -allocator_resident:11584512 -total_system_memory:33378541568 -total_system_memory_human:31.09G -used_memory_lua:31744 -used_memory_vm_eval:31744 -used_memory_lua_human:31.00K -used_memory_scripts_eval:0 -number_of_cached_scripts:0 -number_of_functions:0 -number_of_libraries:0 -used_memory_vm_functions:32768 -used_memory_vm_total:64512 -used_memory_vm_total_human:63.00K -used_memory_functions:216 -used_memory_scripts:216 -used_memory_scripts_human:216B -maxmemory:0 -maxmemory_human:0B -maxmemory_policy:noeviction -allocator_frag_ratio:6.17 -allocator_frag_bytes:9706808 -allocator_rss_ratio:1.00 -allocator_rss_bytes:0 -rss_overhead_ratio:1.00 -rss_overhead_bytes:31744 -mem_fragmentation_ratio:6.19 -mem_fragmentation_bytes:9738552 -mem_not_counted_for_evict:0 -mem_replication_backlog:0 -mem_total_replication_buffers:0 -mem_clients_slaves:0 -mem_clients_normal:0 -mem_cluster_links:0 -mem_aof_buffer:0 -mem_allocator:libc -active_defrag_running:0 -lazyfree_pending_objects:0 -lazyfreed_objects:0 - -# Persistence -loading:0 -async_loading:0 -current_cow_peak:0 -current_cow_size:0 -current_cow_size_age:0 -current_fork_perc:0.00 -current_save_keys_processed:0 -current_save_keys_total:0 -rdb_changes_since_last_save:3 -rdb_bgsave_in_progress:0 -rdb_last_save_time:1723474288 -rdb_last_bgsave_status:ok -rdb_last_bgsave_time_sec:-1 -rdb_current_bgsave_time_sec:-1 -rdb_saves:0 -rdb_last_cow_size:0 -rdb_last_load_keys_expired:0 -rdb_last_load_keys_loaded:0 -aof_enabled:0 -aof_rewrite_in_progress:0 -aof_rewrite_scheduled:0 -aof_last_rewrite_time_sec:-1 -aof_current_rewrite_time_sec:-1 -aof_last_bgrewrite_status:ok -aof_rewrites:0 -aof_rewrites_consecutive_failures:0 -aof_last_write_status:ok -aof_last_cow_size:0 -module_fork_in_progress:0 -module_fork_last_cow_size:0 - -# Stats -total_connections_received:25 -total_commands_processed:77 -instantaneous_ops_per_sec:29 -total_net_input_bytes:172775 -total_net_output_bytes:10686 -total_net_repl_input_bytes:0 -total_net_repl_output_bytes:0 -instantaneous_input_kbps:1.33 -instantaneous_output_kbps:4.69 -instantaneous_input_repl_kbps:0.00 -instantaneous_output_repl_kbps:0.00 -rejected_connections:0 -sync_full:0 -sync_partial_ok:0 -sync_partial_err:0 -expired_keys:0 -expired_stale_perc:0.00 -expired_time_cap_reached_count:0 -expire_cycle_cpu_milliseconds:0 -evicted_keys:0 -evicted_clients:0 -total_eviction_exceeded_time:0 -current_eviction_exceeded_time:0 -keyspace_hits:1 -keyspace_misses:0 -pubsub_channels:0 -pubsub_patterns:0 -pubsubshard_channels:0 -latest_fork_usec:0 -total_forks:0 -migrate_cached_sockets:0 -slave_expires_tracked_keys:0 -active_defrag_hits:0 -active_defrag_misses:0 -active_defrag_key_hits:0 -active_defrag_key_misses:0 -total_active_defrag_time:0 -current_active_defrag_time:0 -tracking_total_keys:0 -tracking_total_items:0 -tracking_total_prefixes:0 -unexpected_error_replies:0 -total_error_replies:2 -dump_payload_sanitizations:0 -total_reads_processed:111 -total_writes_processed:79 -io_threaded_reads_processed:0 -io_threaded_writes_processed:0 -reply_buffer_shrinks:1 -reply_buffer_expands:0 -eventloop_cycles:161 -eventloop_duration_sum:13216 -eventloop_duration_cmd_sum:1423 -instantaneous_eventloop_cycles_per_sec:59 -instantaneous_eventloop_duration_usec:82 -acl_access_denied_auth:0 -acl_access_denied_cmd:0 -acl_access_denied_key:0 -acl_access_denied_channel:0 - -# Replication -role:master -connected_slaves:0 -master_failover_state:no-failover -master_replid:470fb44f23bf3f61b2af16d17bc14e5fa542c5b2 -master_replid2:0000000000000000000000000000000000000000 -master_repl_offset:0 -second_repl_offset:-1 -repl_backlog_active:0 -repl_backlog_size:1048576 -repl_backlog_first_byte_offset:0 -repl_backlog_histlen:0 - -# CPU -used_cpu_sys:0.021820 -used_cpu_user:0.005455 -used_cpu_sys_children:0.000000 -used_cpu_user_children:0.000000 -used_cpu_sys_main_thread:0.021775 -used_cpu_user_main_thread:0.005443 - -# Modules -module:name=ReJSON,ver=999999,api=1,filters=0,usedby=[],using=[],options=[handle-io-errors] - -# Commandstats -cmdstat_json.set:calls=3,usec=67,usec_per_call=22.33,rejected_calls=0,failed_calls=0 -cmdstat_client|setinfo:calls=50,usec=57,usec_per_call=1.14,rejected_calls=0,failed_calls=0 -cmdstat_info:calls=1,usec=27,usec_per_call=27.00,rejected_calls=0,failed_calls=0 -cmdstat_cluster|addslots:calls=1,usec=308,usec_per_call=308.00,rejected_calls=0,failed_calls=0 -cmdstat_cluster|info:calls=20,usec=921,usec_per_call=46.05,rejected_calls=0,failed_calls=0 -cmdstat_cluster|meet:calls=1,usec=43,usec_per_call=43.00,rejected_calls=0,failed_calls=0 -cmdstat_ping:calls=1,usec=0,usec_per_call=0.00,rejected_calls=0,failed_calls=0 - -# Errorstats -errorstat_ERR:count=2 - -# Latencystats -latency_percentiles_usec_json.set:p50=28.031,p99=38.143,p99.9=38.143 -latency_percentiles_usec_client|setinfo:p50=1.003,p99=5.023,p99.9=5.023 -latency_percentiles_usec_info:p50=27.007,p99=27.007,p99.9=27.007 -latency_percentiles_usec_cluster|addslots:p50=309.247,p99=309.247,p99.9=309.247 -latency_percentiles_usec_cluster|info:p50=46.079,p99=99.327,p99.9=99.327 -latency_percentiles_usec_cluster|meet:p50=43.007,p99=43.007,p99.9=43.007 -latency_percentiles_usec_ping:p50=0.001,p99=0.001,p99.9=0.001 - -# Cluster -cluster_enabled:1 - -# Keyspace -db0:keys=1,expires=0,avg_ttl=0 - -# Cluster info -cluster_state:ok -cluster_slots_assigned:16384 -cluster_slots_ok:16384 -cluster_slots_pfail:0 -cluster_slots_fail:0 -cluster_known_nodes:1 -cluster_size:1 -cluster_current_epoch:0 -cluster_my_epoch:0 -cluster_stats_messages_pong_sent:1 -cluster_stats_messages_meet_sent:1 -cluster_stats_messages_sent:2 -cluster_stats_messages_pong_received:1 -cluster_stats_messages_meet_received:1 -cluster_stats_messages_received:2 -total_cluster_links_buffer_limit_exceeded:0 - ------- CLUSTER NODES OUTPUT ------ -75dbf2b0fbe9a363151daa6280bb9679b5a845d3 127.0.0.1:6379@16379,,tls-port=0,shard-id=28351096806fb67929f04318dace2f99aaab441e myself,master - 0 0 0 connected 0-16383 - ------- CLIENT LIST OUTPUT ------ -id=30 addr=127.0.0.1:38544 laddr=127.0.0.1:6379 fd=11 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=40 qbuf-free=16346 argv-mem=18 multi-mem=0 rbs=16392 rbp=16392 obl=0 oll=0 omem=0 tot-mem=33602 events=r cmd=json.get user=default redir=-1 resp=2 lib-name=redis-py lib-ver=5.0.7 - ------- CURRENT CLIENT INFO ------ -id=30 addr=127.0.0.1:38544 laddr=127.0.0.1:6379 fd=11 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=40 qbuf-free=16346 argv-mem=18 multi-mem=0 rbs=16392 rbp=16392 obl=0 oll=0 omem=0 tot-mem=33602 events=r cmd=json.get user=default redir=-1 resp=2 lib-name=redis-py lib-ver=5.0.7 -argc: '3' -argv[0]: '"JSON.GET"' -argv[1]: '"k"' -argv[2]: '"$..[?@>0]"' -929534:M 12 Aug 2024 17:51:30.383 # key 'k' found in DB containing the following object: -929534:M 12 Aug 2024 17:51:30.383 # Object type: 5 -929534:M 12 Aug 2024 17:51:30.383 # Object encoding: 0 -929534:M 12 Aug 2024 17:51:30.383 # Object refcount: 1 - ------- EXECUTING CLIENT INFO ------ -id=30 addr=127.0.0.1:38544 laddr=127.0.0.1:6379 fd=11 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=40 qbuf-free=16346 argv-mem=18 multi-mem=0 rbs=16392 rbp=16392 obl=0 oll=0 omem=0 tot-mem=33602 events=r cmd=json.get user=default redir=-1 resp=2 lib-name=redis-py lib-ver=5.0.7 -argc: '3' -argv[0]: '"JSON.GET"' -argv[1]: '"k"' -argv[2]: '"$..[?@>0]"' -929534:M 12 Aug 2024 17:51:30.383 # key 'k' found in DB containing the following object: -929534:M 12 Aug 2024 17:51:30.383 # Object type: 5 -929534:M 12 Aug 2024 17:51:30.383 # Object encoding: 0 -929534:M 12 Aug 2024 17:51:30.383 # Object refcount: 1 - ------- MODULES INFO OUTPUT ------ -# ReJSON_trace -ReJSON_backtrace: 0: redis_module::add_trace_info - at /home/ephraim_feldblum/.cargo/registry/src/index.crates.io-6f17d22bba15001f/redis-module-2.0.7/src/lib.rs:70:29 - redis_module::basic_info_command_handler - at /home/ephraim_feldblum/.cargo/registry/src/index.crates.io-6f17d22bba15001f/redis-module-2.0.7/src/lib.rs:94:25 - 1: rejson::__info_func - at /home/ephraim_feldblum/.cargo/registry/src/index.crates.io-6f17d22bba15001f/redis-module-2.0.7/src/macros.rs:174:13 - 2: modulesCollectInfo - at /home/ephraim_feldblum/git/redis/src/module.c:10293:9 - 3: logModulesInfo - at /home/ephraim_feldblum/git/redis/src/debug.c:1900:22 - printCrashReport - at /home/ephraim_feldblum/git/redis/src/debug.c:2183:5 - 4: sigsegvHandler - at /home/ephraim_feldblum/git/redis/src/debug.c:2164:5 - 5: - 6: __pthread_kill_implementation - at ./nptl/pthread_kill.c:43:17 - __pthread_kill_internal - at ./nptl/pthread_kill.c:78:10 - __GI___pthread_kill - at ./nptl/pthread_kill.c:89:10 - 7: __GI_raise - at ./signal/../sysdeps/posix/raise.c:26:13 - 8: __GI_abort - at ./stdlib/abort.c:79:7 - 9: std::sys::pal::unix::abort_internal - at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/sys/pal/unix/mod.rs:370:14 - 10: rust_panic - at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/rt.rs:43:13 - 11: std::panicking::rust_panic_with_hook - at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/panicking.rs:817:5 - 12: std::panicking::begin_panic_handler::{{closure}} - at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/panicking.rs:664:13 - 13: std::sys_common::backtrace::__rust_end_short_backtrace - at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/sys_common/backtrace.rs:171:18 - 14: rust_begin_unwind - at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/panicking.rs:652:5 - 15: core::panicking::panic_fmt - at /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/core/src/panicking.rs:72:14 - 16: rejson::key_value::KeyValue::to_json_single - at /home/ephraim_feldblum/git/RedisJSON/redis_json/src/key_value.rs:182:9 - rejson::key_value::KeyValue::to_json - at /home/ephraim_feldblum/git/RedisJSON/redis_json/src/key_value.rs:253:13 - 17: rejson::commands::json_get - at /home/ephraim_feldblum/git/RedisJSON/redis_json/src/commands.rs:142:22 - 18: rejson::RedisModule_OnLoad::__do_command::{{closure}}::{{closure}} - at /home/ephraim_feldblum/git/RedisJSON/redis_json/src/lib.rs:139:36 - rejson::RedisModule_OnLoad::__do_command::{{closure}} - at /home/ephraim_feldblum/git/RedisJSON/redis_json/src/lib.rs:97:27 - rejson::RedisModule_OnLoad::__do_command - at /home/ephraim_feldblum/.cargo/registry/src/index.crates.io-6f17d22bba15001f/redis-module-2.0.7/src/macros.rs:22:28 - 19: RedisModuleCommandDispatcher - at /home/ephraim_feldblum/git/redis/src/module.c:917:5 - 20: call - at /home/ephraim_feldblum/git/redis/src/server.c:3519:5 - 21: processCommand - at /home/ephraim_feldblum/git/redis/src/server.c:4160:9 - 22: processCommandAndResetClient - at /home/ephraim_feldblum/git/redis/src/networking.c:2466:9 - processInputBuffer - at /home/ephraim_feldblum/git/redis/src/networking.c:2574:17 - 23: readQueryFromClient - at /home/ephraim_feldblum/git/redis/src/networking.c:2713:9 - 24: callHandler - at /home/ephraim_feldblum/git/redis/src/./connhelpers.h:79:18 - connSocketEventHandler - at /home/ephraim_feldblum/git/redis/src/socket.c:298:14 - 25: aeProcessEvents - at /home/ephraim_feldblum/git/redis/src/ae.c:436:17 - 26: aeMain - at /home/ephraim_feldblum/git/redis/src/ae.c:496:9 - main - at /home/ephraim_feldblum/git/redis/src/server.c:7360:5 - 27: __libc_start_call_main - at ./csu/../sysdeps/nptl/libc_start_call_main.h:58:16 - 28: __libc_start_main_impl - at ./csu/../csu/libc-start.c:392:3 - 29: _start - - ------- CONFIG DEBUG OUTPUT ------ -lazyfree-lazy-server-del no -sanitize-dump-payload no -io-threads-do-reads no -list-compress-depth 0 -repl-diskless-load disabled -replica-read-only yes -repl-diskless-sync yes -client-query-buffer-limit 1gb -lazyfree-lazy-expire no -slave-read-only yes -io-threads 1 -lazyfree-lazy-eviction no -proto-max-bulk-len 512mb -activedefrag no -lazyfree-lazy-user-del no -lazyfree-lazy-user-flush no - ------- FAST MEMORY TEST ------ -929534:M 12 Aug 2024 17:51:30.458 # Bio worker thread #0 terminated -929534:M 12 Aug 2024 17:51:30.458 # Bio worker thread #1 terminated -929534:M 12 Aug 2024 17:51:30.458 # Bio worker thread #2 terminated -*** Preparing to test memory region 560f0fcbf000 (135168 bytes) -*** Preparing to test memory region 560f1036b000 (49389568 bytes) -*** Preparing to test memory region 7fca60d47000 (6115328 bytes) -*** Preparing to test memory region 7fca61ec0000 (1110016 bytes) -*** Preparing to test memory region 7fca63710000 (3047424 bytes) -*** Preparing to test memory region 7fca63c18000 (8388608 bytes) -*** Preparing to test memory region 7fca64419000 (8388608 bytes) -*** Preparing to test memory region 7fca64c1a000 (8388608 bytes) -*** Preparing to test memory region 7fca65821000 (991232 bytes) -*** Preparing to test memory region 7fca65b8b000 (8192 bytes) -*** Preparing to test memory region 7fca65da9000 (53248 bytes) -*** Preparing to test memory region 7fca661f7000 (12288 bytes) -*** Preparing to test memory region 7fca6639d000 (8192 bytes) -.O.O.O.O.O.O.O.O.O.O.O.O.O -Fast memory test PASSED, however your memory can still be broken. Please run a memory test for several hours if possible. - ------- DUMPING CODE AROUND EIP ------ -Symbol: pthread_kill (base: 0x7fca65c238d0) -Module: /lib/x86_64-linux-gnu/libc.so.6 (base 0x7fca65b8d000) -$ xxd -r -p /tmp/dump.hex /tmp/dump.bin -$ objdump --adjust-vma=0x7fca65c238d0 -D -b binary -m i386:x86-64 /tmp/dump.bin ------- -929534:M 12 Aug 2024 17:51:30.584 # dump of function (hexdump of 428 bytes): -f30f1efa41568d56e0415541bd16000000415455534881ec9000000064488b042528000000488984248800000031c083fa01767e4889fb4189f464483b3c25100000000f84c70000004989e641ba0800000031ffb80e0000004c89f2488d352dc113000f0531c0488dab74090000ba01000000f00fb155000f85ca00000080bb730900000074594531ed31d287937409000083fa010f8fbd00000041ba0800000031d24c89f6bf02000000b80e0000000f05488b84248800000064482b0425280000000f859c0000004881c4900000004489e85b5d415c415d415ec30f1f4000448babd0020000e8845605004489e289c74489eeb8ea0000000f053d00f0ffff76854189c541f7ddeb80660f1f440000b8ba0000000f0589c5e8525605004489e289ee89c7b8ea0000000f054189c541f7dd3d00f0ffffb800000000440f46e8e96dffffff0f1f004889efe810a8ffffe929ffffff0f1f004889efe8d0a8ffffe936ffffffe816fb0900660f1f440000f30f1efac3662e0f1f840000000000904157415641554154554889fd534883ec2864488b042528000000488944241831c0648b0425d0020000894424 -Function at 0x7fca65c79040 is getpid -Function at 0x7fca65cc3550 is __stack_chk_fail - -=== REDIS BUG REPORT END. Make sure to include from START to END. === - - Please report the crash by opening an issue on github: - - http://github.com/redis/redis/issues - - If a Redis module was involved, please open in the module's repo instead. - - Suspect RAM error? Use redis-server --test-memory to verify it. - - Some other issues could be detected by redis-server --check-system - ❌ (FAIL): 'Connection closed by server.' == '[1]' test.py:1538 -test:test_recursive_descent: - [FAIL] - process is not alive, might have crash durring test execution, check this out. server id : 1 - -Test Took: 2 sec -Total Tests Run: 1, Total Tests Failed: 1, Total Tests Passed: 0 -Failed Tests Summary: - test:test_recursive_descent - ❌ (FAIL): 'Connection closed by server.' == '[1]' test.py:1538 From d9ec32c9f03299483d95428d55c5a24ee10673b2 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Tue, 13 Aug 2024 17:32:35 +0300 Subject: [PATCH 07/33] and arr_pop too --- redis_json/src/ivalue_manager.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index b2fe9c0de..d469f18cd 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -443,21 +443,21 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { index: i64, serialize_callback: C, ) -> RedisResult { - let mut res = None; - self.do_op(&path, |v| { - if let Some(array) = v.as_array_mut() { - if array.is_empty() { - return Ok(Some(())); + let res = self + .do_op(&path, |v| { + if let Some(array) = v.as_array_mut() { + if array.is_empty() { + return Ok(Some(None)); + } + // Verify legal index in bounds + let len = array.len() as i64; + let index = normalize_arr_start_index(index, len) as usize; + Ok(Some(array.remove(index))) + } else { + Err(err_json(v, "array")) } - // Verify legal index in bounds - let len = array.len() as i64; - let index = normalize_arr_start_index(index, len) as usize; - res = Some(array.remove(index).unwrap()); - Ok(Some(())) - } else { - Err(err_json(v, "array")) - } - })?; + })? + .flatten(); serialize_callback(res.as_ref()) } From 7e9456e5e21b1f02ea061373ad5f163e498776e5 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Tue, 13 Aug 2024 17:59:06 +0300 Subject: [PATCH 08/33] do_op cannot reach the case where op_fun returns Ok(None) as none of the callbacks ever return such a construct --- redis_json/src/ivalue_manager.rs | 96 +++++++++++++------------------- 1 file changed, 40 insertions(+), 56 deletions(-) diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index d469f18cd..7683e42a4 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -169,23 +169,18 @@ where } impl<'a> IValueKeyHolderWrite<'a> { - fn do_op(&mut self, paths: &[String], mut op_fun: F) -> Result, RedisError> + fn do_op(&mut self, paths: &[String], mut op_fun: F) -> Result where - F: FnMut(&mut IValue) -> Result, Error>, + F: FnMut(&mut IValue) -> Result, { let root = self.get_value().unwrap().unwrap(); if paths.is_empty() { // updating the root require special treatment - op_fun(root) - .map(|r| { - r.or_else(|| { - root.take(); - None - }) - }) - .map_err(|err| RedisError::String(err.msg)) + op_fun(root).map_err(|err| RedisError::String(err.msg)) } else { - update(paths, root, op_fun).into_both() + update(paths, root, |v| Ok(Some(op_fun(v)?))) + .map(|o| o.unwrap()) + .into_both() } } @@ -218,23 +213,18 @@ impl<'a> IValueKeyHolderWrite<'a> { }; let new_val = IValue::from(num_res?); *v = new_val.clone(); - Ok(Some(new_val)) + Ok(new_val) })?; - match res { - None => Err(RedisError::String(err_msg_json_path_doesnt_exist())), - Some(n) => { - if let Some(n) = n.as_number() { - if !n.has_decimal_point() { - Ok(n.to_i64().unwrap().into()) - } else if let Some(f) = n.to_f64() { - Ok(serde_json::Number::from_f64(f).unwrap()) - } else { - Err(RedisError::Str("result is not a number")) - } - } else { - Err(RedisError::Str("result is not a number")) - } + if let Some(n) = res.as_number() { + if !n.has_decimal_point() { + Ok(n.to_i64().unwrap().into()) + } else if let Some(f) = n.to_f64() { + Ok(serde_json::Number::from_f64(f).unwrap()) + } else { + Err(RedisError::Str("result is not a number")) } + } else { + Err(RedisError::Str("result is not a number")) } } else { Err(RedisError::Str("bad input number")) @@ -386,13 +376,12 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { fn str_append(&mut self, path: Vec, val: String) -> Result { let json = serde_json::from_str(&val)?; if let serde_json::Value::String(s) = json { - let res = self.do_op(&path, |v| { + self.do_op(&path, |v| { let v_str = v.as_string_mut().unwrap(); let new_str = [v_str.as_str(), s.as_str()].concat(); *v_str = IString::intern(&new_str); - Ok(Some(new_str.len())) - })?; - res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) + Ok(new_str.len()) + }) } else { Err(RedisError::String(err_msg_json_expected( "string", @@ -402,14 +391,13 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn arr_append(&mut self, path: Vec, args: Vec) -> Result { - let res = self.do_op(&path, |v| { + self.do_op(&path, |v| { let arr = v.as_array_mut().unwrap(); for a in &args { arr.push(a.clone()); } - Ok(Some(arr.len())) - })?; - res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) + Ok(arr.len()) + }) } fn arr_insert( @@ -418,7 +406,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { args: &[IValue], index: i64, ) -> Result { - let res = self.do_op(&paths, |v: &mut IValue| { + self.do_op(&paths, |v: &mut IValue| { // Verify legal index in bounds let len = v.len().unwrap() as i64; let index = if index < 0 { len + index } else { index }; @@ -432,9 +420,8 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { curr.insert(index, a.clone()); index += 1; } - Ok(Some(curr.len())) - })?; - res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) + Ok(curr.len()) + }) } fn arr_pop) -> RedisResult>( @@ -443,26 +430,24 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { index: i64, serialize_callback: C, ) -> RedisResult { - let res = self - .do_op(&path, |v| { - if let Some(array) = v.as_array_mut() { - if array.is_empty() { - return Ok(Some(None)); - } - // Verify legal index in bounds - let len = array.len() as i64; - let index = normalize_arr_start_index(index, len) as usize; - Ok(Some(array.remove(index))) - } else { - Err(err_json(v, "array")) + let res = self.do_op(&path, |v| { + if let Some(array) = v.as_array_mut() { + if array.is_empty() { + return Ok(None); } - })? - .flatten(); + // Verify legal index in bounds + let len = array.len() as i64; + let index = normalize_arr_start_index(index, len) as usize; + Ok(array.remove(index)) + } else { + Err(err_json(v, "array")) + } + })?; serialize_callback(res.as_ref()) } fn arr_trim(&mut self, path: Vec, start: i64, stop: i64) -> Result { - let res = self.do_op(&path, |v| { + self.do_op(&path, |v| { if let Some(array) = v.as_array_mut() { let len = array.len() as i64; let stop = stop.normalize(len); @@ -479,12 +464,11 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { array.rotate_left(range.start); array.truncate(range.end - range.start); - Ok(Some(array.len())) + Ok(array.len()) } else { Err(err_json(v, "array")) } - })?; - res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) + }) } fn clear(&mut self, path: Vec) -> Result { From be1fa9424d2a57b17fb71e8e4bfefc026d5c46d4 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Tue, 13 Aug 2024 18:13:52 +0300 Subject: [PATCH 09/33] manage case where path is invalid --- redis_json/src/ivalue_manager.rs | 104 ++++++++++++++++++------------- 1 file changed, 60 insertions(+), 44 deletions(-) diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index 7683e42a4..d28ab3928 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -169,18 +169,16 @@ where } impl<'a> IValueKeyHolderWrite<'a> { - fn do_op(&mut self, paths: &[String], mut op_fun: F) -> Result + fn do_op(&mut self, paths: &[String], mut op_fun: F) -> Result, RedisError> where F: FnMut(&mut IValue) -> Result, { let root = self.get_value().unwrap().unwrap(); if paths.is_empty() { // updating the root require special treatment - op_fun(root).map_err(|err| RedisError::String(err.msg)) + op_fun(root).into_both() } else { - update(paths, root, |v| Ok(Some(op_fun(v)?))) - .map(|o| o.unwrap()) - .into_both() + update(paths, root, |v| Ok(Some(op_fun(v)?))).into_both() } } @@ -215,16 +213,21 @@ impl<'a> IValueKeyHolderWrite<'a> { *v = new_val.clone(); Ok(new_val) })?; - if let Some(n) = res.as_number() { - if !n.has_decimal_point() { - Ok(n.to_i64().unwrap().into()) - } else if let Some(f) = n.to_f64() { - Ok(serde_json::Number::from_f64(f).unwrap()) - } else { - Err(RedisError::Str("result is not a number")) + match res { + None => Err(RedisError::String(err_msg_json_path_doesnt_exist())), + Some(n) => { + if let Some(n) = n.as_number() { + if !n.has_decimal_point() { + Ok(n.to_i64().unwrap().into()) + } else if let Some(f) = n.to_f64() { + Ok(serde_json::Number::from_f64(f).unwrap()) + } else { + Err(RedisError::Str("result is not a number")) + } + } else { + Err(RedisError::Str("result is not a number")) + } } - } else { - Err(RedisError::Str("result is not a number")) } } else { Err(RedisError::Str("bad input number")) @@ -360,28 +363,32 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn bool_toggle(&mut self, path: Vec) -> Result { - let res = self.do_op(&path, |v| { - if let DestructuredMut::Bool(mut bool_mut) = v.destructure_mut() { - //Using DestructuredMut in order to modify a `Bool` variant - let val = bool_mut.get() ^ true; - bool_mut.set(val); - Ok(Some(val)) - } else { - Ok(None) - } - })?; + let res = self + .do_op(&path, |v| { + if let DestructuredMut::Bool(mut bool_mut) = v.destructure_mut() { + //Using DestructuredMut in order to modify a `Bool` variant + let val = bool_mut.get() ^ true; + bool_mut.set(val); + Ok(Some(val)) + } else { + Ok(None) + } + })? + .flatten(); res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) } fn str_append(&mut self, path: Vec, val: String) -> Result { let json = serde_json::from_str(&val)?; if let serde_json::Value::String(s) = json { - self.do_op(&path, |v| { + let res = self.do_op(&path, |v| { let v_str = v.as_string_mut().unwrap(); let new_str = [v_str.as_str(), s.as_str()].concat(); *v_str = IString::intern(&new_str); Ok(new_str.len()) - }) + })?; + + res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) } else { Err(RedisError::String(err_msg_json_expected( "string", @@ -391,13 +398,15 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn arr_append(&mut self, path: Vec, args: Vec) -> Result { - self.do_op(&path, |v| { + let res = self.do_op(&path, |v| { let arr = v.as_array_mut().unwrap(); for a in &args { arr.push(a.clone()); } Ok(arr.len()) - }) + })?; + + res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) } fn arr_insert( @@ -406,7 +415,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { args: &[IValue], index: i64, ) -> Result { - self.do_op(&paths, |v: &mut IValue| { + let res = self.do_op(&paths, |v: &mut IValue| { // Verify legal index in bounds let len = v.len().unwrap() as i64; let index = if index < 0 { len + index } else { index }; @@ -421,7 +430,9 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { index += 1; } Ok(curr.len()) - }) + })?; + + res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) } fn arr_pop) -> RedisResult>( @@ -430,24 +441,26 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { index: i64, serialize_callback: C, ) -> RedisResult { - let res = self.do_op(&path, |v| { - if let Some(array) = v.as_array_mut() { - if array.is_empty() { - return Ok(None); + let res = self + .do_op(&path, |v| { + if let Some(array) = v.as_array_mut() { + if array.is_empty() { + return Ok(None); + } + // Verify legal index in bounds + let len = array.len() as i64; + let index = normalize_arr_start_index(index, len) as usize; + Ok(array.remove(index)) + } else { + Err(err_json(v, "array")) } - // Verify legal index in bounds - let len = array.len() as i64; - let index = normalize_arr_start_index(index, len) as usize; - Ok(array.remove(index)) - } else { - Err(err_json(v, "array")) - } - })?; + })? + .flatten(); serialize_callback(res.as_ref()) } fn arr_trim(&mut self, path: Vec, start: i64, stop: i64) -> Result { - self.do_op(&path, |v| { + let res = self.do_op(&path, |v| { if let Some(array) = v.as_array_mut() { let len = array.len() as i64; let stop = stop.normalize(len); @@ -468,7 +481,9 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } else { Err(err_json(v, "array")) } - }) + })?; + + res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) } fn clear(&mut self, path: Vec) -> Result { @@ -490,6 +505,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } _ => Ok(None), })? + .flatten() .unwrap_or(0); Ok(cleared) } From e05eca67eb99edc6c3e22f8b0d93950e64d776af Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Thu, 15 Aug 2024 17:49:14 +0300 Subject: [PATCH 10/33] work --- redis_json/src/commands.rs | 85 +++++++++-------- redis_json/src/ivalue_manager.rs | 151 +++++++++++++------------------ redis_json/src/manager.rs | 58 +++++------- 3 files changed, 132 insertions(+), 162 deletions(-) diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index 77dcbbcbf..d2ede9088 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -254,24 +254,26 @@ pub fn json_merge(manager: M, ctx: &Context, args: Vec) let mut update_info = KeyValue::new(doc).find_paths(path.get_path(), SetOptions::MergeExisting)?; if !update_info.is_empty() { - let mut res = false; - if update_info.len() == 1 { - res = match update_info.pop().unwrap() { + let res = if update_info.len() == 1 { + match update_info.pop().unwrap() { UpdateInfo::SUI(sui) => redis_key.merge_value(sui.path, val)?, UpdateInfo::AUI(aui) => redis_key.dict_add(aui.path, &aui.key, val)?, } } else { - for ui in update_info { - res = match ui { - UpdateInfo::SUI(sui) => { - redis_key.merge_value(sui.path, val.clone())? - } - UpdateInfo::AUI(aui) => { - redis_key.dict_add(aui.path, &aui.key, val.clone())? - } - } || res; // If any of the updates succeed, return true - } - } + update_info + .into_iter() + .try_fold(false, |res, ui| -> RedisResult<_> { + let updated = match ui { + UpdateInfo::SUI(sui) => { + redis_key.merge_value(sui.path, val.clone())? + } + UpdateInfo::AUI(aui) => { + redis_key.dict_add(aui.path, &aui.key, val.clone())? + } + }; + Ok(updated || res) // If any of the updates succeed, return true + })? + }; if res { redis_key.notify_keyspace_event(ctx, "json.merge")?; manager.apply_changes(ctx); @@ -383,7 +385,7 @@ fn find_paths bool>( path: &str, doc: &T, mut f: F, -) -> Result>, RedisError> { +) -> RedisResult>> { let query = compile(path).map_err(|e| RedisError::String(e.to_string()))?; let res = calc_once_with_paths(query, doc) .into_iter() @@ -397,7 +399,7 @@ fn find_paths bool>( fn get_all_values_and_paths<'a, T: SelectValue>( path: &str, doc: &'a T, -) -> Result)>, RedisError> { +) -> RedisResult)>> { let query = compile(path).map_err(|e| RedisError::String(e.to_string()))?; let res = calc_once_with_paths(query, doc) .into_iter() @@ -432,7 +434,7 @@ fn find_all_paths( path: &str, doc: &T, f: F, -) -> Result>>, RedisError> +) -> RedisResult>>> where F: Fn(&T) -> bool, { @@ -444,7 +446,7 @@ fn find_all_values<'a, T: SelectValue, F>( path: &str, doc: &'a T, f: F, -) -> Result>, RedisError> +) -> RedisResult>> where F: Fn(&T) -> bool, { @@ -711,7 +713,7 @@ fn json_num_op_impl( number: &str, op: NumOp, cmd: &str, -) -> Result>, RedisError> +) -> RedisResult>> where M: Manager, { @@ -763,25 +765,30 @@ where .get_value()? .ok_or_else(RedisError::nonexistent_key)?; let paths = find_paths(path, root, |v| { - v.get_type() == SelectValueType::Double || v.get_type() == SelectValueType::Long + matches!( + v.get_type(), + SelectValueType::Double | SelectValueType::Long, + ) })?; - if !paths.is_empty() { - let mut res = None; - for p in paths { - res = Some(match op { - NumOp::Incr => redis_key.incr_by(p, number)?, - NumOp::Mult => redis_key.mult_by(p, number)?, - NumOp::Pow => redis_key.pow_by(p, number)?, - }); - } - redis_key.notify_keyspace_event(ctx, cmd)?; - manager.apply_changes(ctx); - Ok(res.unwrap().to_string().into()) - } else { - Err(RedisError::String( - err_msg_json_path_doesnt_exist_with_param_or(path, "does not contains a number"), - )) - } + let res = paths + .into_iter() + .try_fold(None, |_, p| { + match op { + NumOp::Incr => redis_key.incr_by(p, number), + NumOp::Mult => redis_key.mult_by(p, number), + NumOp::Pow => redis_key.pow_by(p, number), + } + .into_both() + }) + .transpose() + .unwrap_or_else(|| { + Err(RedisError::String( + err_msg_json_path_doesnt_exist_with_param_or(path, "does not contains a number"), + )) + })?; + redis_key.notify_keyspace_event(ctx, cmd)?; + manager.apply_changes(ctx); + Ok(res.to_string().into()) } /// @@ -1053,7 +1060,7 @@ pub fn json_arr_append( args.peek().ok_or(RedisError::WrongArity)?; let args = args - .map(|arg| -> Result<_, RedisError> { + .map(|arg| -> RedisResult<_> { let json = arg.try_as_str()?; let sv_holder = manager.from_str(json, Format::JSON, true)?; Ok(sv_holder) @@ -1217,7 +1224,7 @@ pub fn json_arr_insert( // We require at least one JSON item to insert args.peek().ok_or(RedisError::WrongArity)?; let args = args - .map(|arg| -> Result<_, RedisError> { + .map(|arg| -> RedisResult<_> { let json = arg.try_as_str()?; let sv_holder = manager.from_str(json, Format::JSON, true)?; Ok(sv_holder) diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index d28ab3928..0212f4a38 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -42,11 +42,10 @@ pub struct IValueKeyHolderWrite<'a> { /// If the returned value from `func` is [`None`], the current value is removed. /// If the returned value from `func` is [`Err`], the current value remains (although it could be modified by `func`) /// -fn replace Result, Error>>( - path: &[String], - root: &mut IValue, - mut func: F, -) -> Result<(), Error> { +fn replace(path: &[String], root: &mut IValue, mut func: F) -> Result +where + F: FnMut(&mut IValue) -> Result, +{ let mut target = root; let last_index = path.len().saturating_sub(1); @@ -59,13 +58,10 @@ fn replace Result, Error>>( if is_last { if let Entry::Occupied(mut e) = obj.entry(token) { let v = e.get_mut(); - if let Some(res) = func(v)? { - *v = res; - } else { - e.remove(); - } + *v = func(v)?; + return Ok(true); } - return Ok(()); + return Ok(false); } obj.get_mut(token.as_str()) } @@ -78,13 +74,10 @@ fn replace Result, Error>>( if is_last { if idx < arr.len() { let v = &mut arr.as_mut_slice()[idx]; - if let Some(res) = func(v)? { - *v = res; - } else { - arr.remove(idx); - } + *v = func(v)?; + return Ok(true); } - return Ok(()); + return Ok(false); } arr.get_mut(idx) } @@ -98,7 +91,7 @@ fn replace Result, Error>>( } } - Ok(()) + Ok(false) } /// @@ -169,11 +162,11 @@ where } impl<'a> IValueKeyHolderWrite<'a> { - fn do_op(&mut self, paths: &[String], mut op_fun: F) -> Result, RedisError> + fn do_op(&mut self, paths: &[String], mut op_fun: F) -> RedisResult> where F: FnMut(&mut IValue) -> Result, { - let root = self.get_value().unwrap().unwrap(); + let root = self.get_value()?.unwrap(); if paths.is_empty() { // updating the root require special treatment op_fun(root).into_both() @@ -188,7 +181,7 @@ impl<'a> IValueKeyHolderWrite<'a> { num: &str, mut op1_fun: F1, mut op2_fun: F2, - ) -> Result + ) -> RedisResult where F1: FnMut(i64, i64) -> i64, F2: FnMut(f64, f64) -> f64, @@ -213,35 +206,33 @@ impl<'a> IValueKeyHolderWrite<'a> { *v = new_val.clone(); Ok(new_val) })?; - match res { - None => Err(RedisError::String(err_msg_json_path_doesnt_exist())), - Some(n) => { - if let Some(n) = n.as_number() { - if !n.has_decimal_point() { - Ok(n.to_i64().unwrap().into()) - } else if let Some(f) = n.to_f64() { - Ok(serde_json::Number::from_f64(f).unwrap()) - } else { - Err(RedisError::Str("result is not a number")) - } - } else { - Err(RedisError::Str("result is not a number")) - } - } - } + res.map_or_else( + || Err(RedisError::String(err_msg_json_path_doesnt_exist())), + |n| { + n.as_number() + .and_then(|n| { + if n.has_decimal_point() { + n.to_f64().and_then(serde_json::Number::from_f64) + } else { + n.to_i64().map(Into::into) + } + }) + .ok_or_else(|| RedisError::Str("result is not a number")) + }, + ) } else { Err(RedisError::Str("bad input number")) } } - fn get_json_holder(&mut self) -> Result<(), RedisError> { + fn get_json_holder(&mut self) -> RedisResult<()> { if self.val.is_none() { self.val = self.key.get_value::>(&REDIS_JSON_TYPE)?; } Ok(()) } - fn set_root(&mut self, v: Option) -> Result<(), RedisError> { + fn set_root(&mut self, v: Option) -> RedisResult<()> { match v { Some(inner) => { self.get_json_holder()?; @@ -262,60 +253,50 @@ impl<'a> IValueKeyHolderWrite<'a> { } impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { - fn notify_keyspace_event(self, ctx: &Context, command: &str) -> Result<(), RedisError> { + fn notify_keyspace_event(self, ctx: &Context, command: &str) -> RedisResult<()> { match ctx.notify_keyspace_event(NotifyEvent::MODULE, command, &self.key_name) { Status::Ok => Ok(()), Status::Err => Err(RedisError::Str("failed notify key space event")), } } - fn delete(&mut self) -> Result<(), RedisError> { + fn delete(&mut self) -> RedisResult<()> { self.key.delete().and(Ok(())) } - fn get_value(&mut self) -> Result, RedisError> { + fn get_value(&mut self) -> RedisResult> { self.get_json_holder()?; let val = self.val.as_mut().map(|v| &mut v.data); Ok(val) } - fn set_value(&mut self, path: Vec, mut v: IValue) -> Result { - let mut updated = false; - if path.is_empty() { + fn set_value(&mut self, path: Vec, mut v: IValue) -> RedisResult { + let updated = if path.is_empty() { // update the root self.set_root(Some(v))?; - updated = true; + true } else { - replace(&path, self.get_value()?.unwrap(), |_v| { - updated = true; - Ok(Some(v.take())) - })?; - } + replace(&path, self.get_value()?.unwrap(), |_| Ok(v.take()))? + }; Ok(updated) } - fn merge_value(&mut self, path: Vec, v: IValue) -> Result { - let mut updated = false; - if path.is_empty() { - merge(self.get_value()?.unwrap(), &v); + fn merge_value(&mut self, path: Vec, v: IValue) -> RedisResult { + let root = self.get_value()?.unwrap(); + let updated = if path.is_empty() { // update the root - updated = true; + merge(root, &v); + true } else { - replace(&path, self.get_value()?.unwrap(), |current| { - updated = true; + replace(&path, root, |current| { merge(current, &v); - Ok(Some(current.take())) - })?; - } + Ok(current.take()) + })? + }; Ok(updated) } - fn dict_add( - &mut self, - path: Vec, - key: &str, - mut v: IValue, - ) -> Result { + fn dict_add(&mut self, path: Vec, key: &str, mut v: IValue) -> RedisResult { let mut updated = false; if path.is_empty() { // update the root @@ -341,7 +322,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { Ok(updated) } - fn delete_path(&mut self, path: Vec) -> Result { + fn delete_path(&mut self, path: Vec) -> RedisResult { let mut deleted = false; update(&path, self.get_value().unwrap().unwrap(), |_v| { deleted = true; // might delete more than a single value @@ -350,19 +331,19 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { Ok(deleted) } - fn incr_by(&mut self, path: Vec, num: &str) -> Result { + fn incr_by(&mut self, path: Vec, num: &str) -> RedisResult { self.do_num_op(path, num, i64::wrapping_add, |f1, f2| f1 + f2) } - fn mult_by(&mut self, path: Vec, num: &str) -> Result { + fn mult_by(&mut self, path: Vec, num: &str) -> RedisResult { self.do_num_op(path, num, i64::wrapping_mul, |f1, f2| f1 * f2) } - fn pow_by(&mut self, path: Vec, num: &str) -> Result { + fn pow_by(&mut self, path: Vec, num: &str) -> RedisResult { self.do_num_op(path, num, |i1, i2| i1.pow(i2 as u32), f64::powf) } - fn bool_toggle(&mut self, path: Vec) -> Result { + fn bool_toggle(&mut self, path: Vec) -> RedisResult { let res = self .do_op(&path, |v| { if let DestructuredMut::Bool(mut bool_mut) = v.destructure_mut() { @@ -378,7 +359,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) } - fn str_append(&mut self, path: Vec, val: String) -> Result { + fn str_append(&mut self, path: Vec, val: String) -> RedisResult { let json = serde_json::from_str(&val)?; if let serde_json::Value::String(s) = json { let res = self.do_op(&path, |v| { @@ -397,7 +378,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } } - fn arr_append(&mut self, path: Vec, args: Vec) -> Result { + fn arr_append(&mut self, path: Vec, args: Vec) -> RedisResult { let res = self.do_op(&path, |v| { let arr = v.as_array_mut().unwrap(); for a in &args { @@ -414,7 +395,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { paths: Vec, args: &[IValue], index: i64, - ) -> Result { + ) -> RedisResult { let res = self.do_op(&paths, |v: &mut IValue| { // Verify legal index in bounds let len = v.len().unwrap() as i64; @@ -459,7 +440,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { serialize_callback(res.as_ref()) } - fn arr_trim(&mut self, path: Vec, start: i64, stop: i64) -> Result { + fn arr_trim(&mut self, path: Vec, start: i64, stop: i64) -> RedisResult { let res = self.do_op(&path, |v| { if let Some(array) = v.as_array_mut() { let len = array.len() as i64; @@ -486,7 +467,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) } - fn clear(&mut self, path: Vec) -> Result { + fn clear(&mut self, path: Vec) -> RedisResult { let cleared = self .do_op(&path, |v| match v.type_() { ValueType::Object => { @@ -516,7 +497,7 @@ pub struct IValueKeyHolderRead { } impl ReadHolder for IValueKeyHolderRead { - fn get_value(&self) -> Result, RedisError> { + fn get_value(&self) -> RedisResult> { let data = self .key .get_value::>(&REDIS_JSON_TYPE)? @@ -554,11 +535,7 @@ impl<'a> Manager for RedisIValueJsonKeyManager<'a> { type V = IValue; type O = IValue; - fn open_key_read( - &self, - ctx: &Context, - key: &RedisString, - ) -> Result { + fn open_key_read(&self, ctx: &Context, key: &RedisString) -> RedisResult { let key = ctx.open_key(key); Ok(IValueKeyHolderRead { key }) } @@ -568,7 +545,7 @@ impl<'a> Manager for RedisIValueJsonKeyManager<'a> { ctx: &Context, key: &RedisString, flags: KeyFlags, - ) -> Result { + ) -> RedisResult { let key = ctx.open_key_with_flags(key, flags); Ok(IValueKeyHolderRead { key }) } @@ -577,7 +554,7 @@ impl<'a> Manager for RedisIValueJsonKeyManager<'a> { &self, ctx: &Context, key: RedisString, - ) -> Result, RedisError> { + ) -> RedisResult> { let key_ptr = ctx.open_key_writable(&key); Ok(IValueKeyHolderWrite { key: key_ptr, @@ -625,7 +602,7 @@ impl<'a> Manager for RedisIValueJsonKeyManager<'a> { /// /// following https://github.com/Diggsey/ijson/issues/23#issuecomment-1377270111 /// - fn get_memory(&self, v: &Self::V) -> Result { + fn get_memory(&self, v: &Self::V) -> RedisResult { let res = size_of::() + match v.type_() { ValueType::Null | ValueType::Bool => 0, @@ -676,7 +653,7 @@ impl<'a> Manager for RedisIValueJsonKeyManager<'a> { Ok(res) } - fn is_json(&self, key: *mut RedisModuleKey) -> Result { + fn is_json(&self, key: *mut RedisModuleKey) -> RedisResult { Ok(verify_type(key, &REDIS_JSON_TYPE).is_ok()) } } diff --git a/redis_json/src/manager.rs b/redis_json/src/manager.rs index a90e7e8ff..dd8b54b4a 100644 --- a/redis_json/src/manager.rs +++ b/redis_json/src/manager.rs @@ -9,7 +9,6 @@ use redis_module::key::KeyFlags; use serde_json::Number; use redis_module::raw::RedisModuleKey; -use redis_module::rediserror::RedisError; use redis_module::{Context, RedisResult, RedisString}; use crate::Format; @@ -33,37 +32,32 @@ pub enum UpdateInfo { } pub trait ReadHolder { - fn get_value(&self) -> Result, RedisError>; + fn get_value(&self) -> RedisResult>; } pub trait WriteHolder { - fn delete(&mut self) -> Result<(), RedisError>; - fn get_value(&mut self) -> Result, RedisError>; - fn set_value(&mut self, path: Vec, v: O) -> Result; - fn merge_value(&mut self, path: Vec, v: O) -> Result; - fn dict_add(&mut self, path: Vec, key: &str, v: O) -> Result; - fn delete_path(&mut self, path: Vec) -> Result; - fn incr_by(&mut self, path: Vec, num: &str) -> Result; - fn mult_by(&mut self, path: Vec, num: &str) -> Result; - fn pow_by(&mut self, path: Vec, num: &str) -> Result; - fn bool_toggle(&mut self, path: Vec) -> Result; - fn str_append(&mut self, path: Vec, val: String) -> Result; - fn arr_append(&mut self, path: Vec, args: Vec) -> Result; - fn arr_insert( - &mut self, - path: Vec, - args: &[O], - index: i64, - ) -> Result; + fn delete(&mut self) -> RedisResult<()>; + fn get_value(&mut self) -> RedisResult>; + fn set_value(&mut self, path: Vec, v: O) -> RedisResult; + fn merge_value(&mut self, path: Vec, v: O) -> RedisResult; + fn dict_add(&mut self, path: Vec, key: &str, v: O) -> RedisResult; + fn delete_path(&mut self, path: Vec) -> RedisResult; + fn incr_by(&mut self, path: Vec, num: &str) -> RedisResult; + fn mult_by(&mut self, path: Vec, num: &str) -> RedisResult; + fn pow_by(&mut self, path: Vec, num: &str) -> RedisResult; + fn bool_toggle(&mut self, path: Vec) -> RedisResult; + fn str_append(&mut self, path: Vec, val: String) -> RedisResult; + fn arr_append(&mut self, path: Vec, args: Vec) -> RedisResult; + fn arr_insert(&mut self, path: Vec, args: &[O], index: i64) -> RedisResult; fn arr_pop) -> RedisResult>( &mut self, path: Vec, index: i64, serialize_callback: C, ) -> RedisResult; - fn arr_trim(&mut self, path: Vec, start: i64, stop: i64) -> Result; - fn clear(&mut self, path: Vec) -> Result; - fn notify_keyspace_event(self, ctx: &Context, command: &str) -> Result<(), RedisError>; + fn arr_trim(&mut self, path: Vec, start: i64, stop: i64) -> RedisResult; + fn clear(&mut self, path: Vec) -> RedisResult; + fn notify_keyspace_event(self, ctx: &Context, command: &str) -> RedisResult<()>; } pub trait Manager { @@ -76,27 +70,19 @@ pub trait Manager { type O: Clone; type WriteHolder: WriteHolder; type ReadHolder: ReadHolder; - fn open_key_read( - &self, - ctx: &Context, - key: &RedisString, - ) -> Result; + fn open_key_read(&self, ctx: &Context, key: &RedisString) -> RedisResult; fn open_key_read_with_flags( &self, ctx: &Context, key: &RedisString, flags: KeyFlags, - ) -> Result; - fn open_key_write( - &self, - ctx: &Context, - key: RedisString, - ) -> Result; + ) -> RedisResult; + fn open_key_write(&self, ctx: &Context, key: RedisString) -> RedisResult; fn apply_changes(&self, ctx: &Context); #[allow(clippy::wrong_self_convention)] fn from_str(&self, val: &str, format: Format, limit_depth: bool) -> Result; - fn get_memory(&self, v: &Self::V) -> Result; - fn is_json(&self, key: *mut RedisModuleKey) -> Result; + fn get_memory(&self, v: &Self::V) -> RedisResult; + fn is_json(&self, key: *mut RedisModuleKey) -> RedisResult; } pub(crate) fn err_json(value: &V, expected_value: &'static str) -> Error { From 6b89dbee3058fa6c3bae4033461608f9398903db Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Sun, 18 Aug 2024 18:23:22 +0300 Subject: [PATCH 11/33] ridding unwraps --- redis_json/src/commands.rs | 96 ++++++++-------- redis_json/src/ivalue_manager.rs | 190 +++++++++++++++++-------------- 2 files changed, 152 insertions(+), 134 deletions(-) diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index d2ede9088..b509a6bb4 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -251,29 +251,29 @@ pub fn json_merge(manager: M, ctx: &Context, args: Vec) manager.apply_changes(ctx); REDIS_OK } else { - let mut update_info = + let update_info = KeyValue::new(doc).find_paths(path.get_path(), SetOptions::MergeExisting)?; if !update_info.is_empty() { let res = if update_info.len() == 1 { - match update_info.pop().unwrap() { - UpdateInfo::SUI(sui) => redis_key.merge_value(sui.path, val)?, - UpdateInfo::AUI(aui) => redis_key.dict_add(aui.path, &aui.key, val)?, + match update_info.into_iter().next().unwrap() { + UpdateInfo::SUI(sui) => redis_key.merge_value(sui.path, val), + UpdateInfo::AUI(aui) => redis_key.dict_add(aui.path, &aui.key, val), } } else { update_info .into_iter() .try_fold(false, |res, ui| -> RedisResult<_> { - let updated = match ui { + match ui { UpdateInfo::SUI(sui) => { - redis_key.merge_value(sui.path, val.clone())? + redis_key.merge_value(sui.path, val.clone()) } UpdateInfo::AUI(aui) => { - redis_key.dict_add(aui.path, &aui.key, val.clone())? + redis_key.dict_add(aui.path, &aui.key, val.clone()) } - }; - Ok(updated || res) // If any of the updates succeed, return true - })? - }; + } + .and_then(|updated| Ok(updated || res)) // If any of the updates succeed, return true + }) + }?; if res { redis_key.notify_keyspace_event(ctx, "json.merge")?; manager.apply_changes(ctx); @@ -358,20 +358,19 @@ pub fn json_mset(manager: M, ctx: &Context, args: Vec) res } -fn apply_updates( - redis_key: &mut M::WriteHolder, - value: M::O, - mut update_info: Vec, -) -> bool { +fn apply_updates(redis_key: &mut M::WriteHolder, value: M::O, ui: Vec) -> bool +where + M: Manager, +{ // If there is only one update info, we can avoid cloning the value - if update_info.len() == 1 { - match update_info.pop().unwrap() { + if ui.len() == 1 { + match ui.into_iter().next().unwrap() { UpdateInfo::SUI(sui) => redis_key.set_value(sui.path, value), UpdateInfo::AUI(aui) => redis_key.dict_add(aui.path, &aui.key, value), } .unwrap_or(false) } else { - update_info.into_iter().fold(false, |updated, ui| { + ui.into_iter().fold(false, |updated, ui| { match ui { UpdateInfo::SUI(sui) => redis_key.set_value(sui.path, value.clone()), UpdateInfo::AUI(aui) => redis_key.dict_add(aui.path, &aui.key, value.clone()), @@ -381,16 +380,19 @@ fn apply_updates( } } -fn find_paths bool>( - path: &str, - doc: &T, - mut f: F, -) -> RedisResult>> { +fn find_paths(path: &str, doc: &T, mut f: F) -> RedisResult>> +where + T: SelectValue, + F: FnMut(&T) -> bool, +{ let query = compile(path).map_err(|e| RedisError::String(e.to_string()))?; let res = calc_once_with_paths(query, doc) .into_iter() - .filter(|e| f(e.res)) - .map(|e| e.path_tracker.unwrap().to_string_path()) + .filter_map(|e| { + f(e.res) + .then(|| e.path_tracker.map(UserPathTracker::to_string_path)) + .flatten() + }) .collect(); Ok(res) } @@ -857,17 +859,16 @@ where .get_value()? .ok_or_else(RedisError::nonexistent_key)?; let paths = find_all_paths(path, root, |v| v.get_type() == SelectValueType::Bool)?; - let mut res: Vec = vec![]; let mut need_notify = false; - for p in paths { - res.push(match p { - Some(p) => { + let res: Vec<_> = paths + .into_iter() + .map(|p| { + p.map_or(Ok(RedisValue::Null), |p| -> RedisResult { need_notify = true; - RedisValue::Integer((redis_key.bool_toggle(p)?).into()) - } - None => RedisValue::Null, - }); - } + redis_key.bool_toggle(p).map(Into::into) + }) + }) + .try_collect()?; if need_notify { redis_key.notify_keyspace_event(ctx, "json.toggle")?; manager.apply_changes(ctx); @@ -953,17 +954,16 @@ where let paths = find_all_paths(path, root, |v| v.get_type() == SelectValueType::String)?; - let mut res: Vec = vec![]; let mut need_notify = false; - for p in paths { - res.push(match p { - Some(p) => { + let res: Vec<_> = paths + .into_iter() + .map(|p| -> RedisResult { + p.map_or(Ok(RedisValue::Null), |p| { need_notify = true; - (redis_key.str_append(p, json.to_string())?).into() - } - _ => RedisValue::Null, - }); - } + redis_key.str_append(p, json.to_string()).map(Into::into) + }) + }) + .try_collect()?; if need_notify { redis_key.notify_keyspace_event(ctx, "json.strappend")?; manager.apply_changes(ctx); @@ -1026,10 +1026,10 @@ where .get_value()? .ok_or_else(RedisError::nonexistent_key)?; let values = find_all_values(path, root, |v| v.get_type() == SelectValueType::String)?; - let mut res: Vec = vec![]; - for v in values { - res.push(v.map_or(RedisValue::Null, |v| (v.get_str().len() as i64).into())); - } + let res: Vec<_> = values + .into_iter() + .map(|v| v.map_or(RedisValue::Null, |v| v.get_str().len().into())) + .collect(); Ok(res.into()) } diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index 0212f4a38..c8b3075d8 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -38,8 +38,6 @@ pub struct IValueKeyHolderWrite<'a> { /// Replaces a value at a given `path`, starting from `root` /// /// The new value is the value returned from `func`, which is called on the current value. -/// -/// If the returned value from `func` is [`None`], the current value is removed. /// If the returned value from `func` is [`Err`], the current value remains (although it could be modified by `func`) /// fn replace(path: &[String], root: &mut IValue, mut func: F) -> Result @@ -52,9 +50,8 @@ where for (i, token) in path.iter().enumerate() { let target_once = target; let is_last = i == last_index; - let target_opt = match target_once.type_() { - ValueType::Object => { - let obj = target_once.as_object_mut().unwrap(); + let target_opt = match target_once.destructure_mut() { + DestructuredMut::Object(obj) => { if is_last { if let Entry::Occupied(mut e) = obj.entry(token) { let v = e.get_mut(); @@ -65,8 +62,7 @@ where } obj.get_mut(token.as_str()) } - ValueType::Array => { - let arr = target_once.as_array_mut().unwrap(); + DestructuredMut::Array(arr) => { let idx = token.parse::().expect(&format!( "An array index is parsed successfully. Array = {:?}, index = {:?}", arr, token @@ -98,12 +94,11 @@ where /// Updates a value at a given `path`, starting from `root` /// /// The value is modified by `func`, which is called on the current value. -/// If the returned value from `func` is [`None`], the current value is removed. /// If the returned value from `func` is [`Err`], the current value remains (although it could be modified by `func`) /// fn update(path: &[String], root: &mut IValue, mut func: F) -> Result, Error> where - F: FnMut(&mut IValue) -> Result, Error>, + F: FnMut(&mut IValue) -> Result, { let mut target = root; @@ -111,40 +106,30 @@ where for (i, token) in path.iter().enumerate() { let target_once = target; let is_last = i == last_index; - let target_opt = match target_once.type_() { - ValueType::Object => { - let obj = target_once.as_object_mut().unwrap(); + let target_opt = match target_once.destructure_mut() { + DestructuredMut::Object(obj) => { if is_last { - let res = if let Entry::Occupied(mut e) = obj.entry(token) { + if let Entry::Occupied(mut e) = obj.entry(token) { let v = e.get_mut(); - func(v)?.or_else(|| { - e.remove(); - None - }) + return func(v).map(Some); } else { - None - }; - return Ok(res); + return Ok(None); + } } obj.get_mut(token.as_str()) } - ValueType::Array => { - let arr = target_once.as_array_mut().unwrap(); + DestructuredMut::Array(arr) => { let idx = token.parse::().expect(&format!( "An array index is parsed successfully. Array = {:?}, index = {:?}", arr, token )); if is_last { - let res = if idx < arr.len() { + if idx < arr.len() { let v = &mut arr.as_mut_slice()[idx]; - func(v)?.or_else(|| { - arr.remove(idx); - None - }) + return func(v).map(Some); } else { - None - }; - return Ok(res); + return Ok(None); + } } arr.get_mut(idx) } @@ -161,6 +146,46 @@ where Ok(None) } +/// +/// Removes a value at a given `path`, starting from `root` +/// +fn remove(path: &[String], root: &mut IValue) -> Result { + let mut target = root; + + let last_index = path.len().saturating_sub(1); + for (i, token) in path.iter().enumerate() { + let target_once = target; + let is_last = i == last_index; + let target_opt = match target_once.destructure_mut() { + DestructuredMut::Object(obj) => { + if is_last { + return Ok(obj.remove(token.as_str()).is_some()); + } + obj.get_mut(token.as_str()) + } + DestructuredMut::Array(arr) => { + let idx = token.parse::().expect(&format!( + "An array index is parsed successfully. Array = {:?}, index = {:?}", + arr, token + )); + if is_last { + return Ok(arr.remove(idx).is_some()); + } + arr.get_mut(idx) + } + _ => None, + }; + + if let Some(t) = target_opt { + target = t; + } else { + break; + } + } + + Ok(false) +} + impl<'a> IValueKeyHolderWrite<'a> { fn do_op(&mut self, paths: &[String], mut op_fun: F) -> RedisResult> where @@ -171,7 +196,7 @@ impl<'a> IValueKeyHolderWrite<'a> { // updating the root require special treatment op_fun(root).into_both() } else { - update(paths, root, |v| Ok(Some(op_fun(v)?))).into_both() + update(paths, root, op_fun).into_both() } } @@ -233,20 +258,16 @@ impl<'a> IValueKeyHolderWrite<'a> { } fn set_root(&mut self, v: Option) -> RedisResult<()> { - match v { - Some(inner) => { - self.get_json_holder()?; - match &mut self.val { - Some(v) => v.data = inner, - None => self - .key - .set_value(&REDIS_JSON_TYPE, RedisJSON { data: inner })?, - } - } - None => { - self.val = None; - self.key.delete()?; + if let Some(data) = v { + self.get_json_holder()?; + if let Some(val) = &mut self.val { + val.data = data + } else { + self.key.set_value(&REDIS_JSON_TYPE, RedisJSON { data })? } + } else { + self.val = None; + self.key.delete()?; } Ok(()) } @@ -271,25 +292,23 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn set_value(&mut self, path: Vec, mut v: IValue) -> RedisResult { - let updated = if path.is_empty() { + if path.is_empty() { // update the root - self.set_root(Some(v))?; - true + self.set_root(Some(v)).and(Ok(true)) } else { - replace(&path, self.get_value()?.unwrap(), |_| Ok(v.take()))? - }; - Ok(updated) + replace(&path, self.get_value()?.unwrap(), |_| Ok(v.take())).map_err(Into::into) + } } - fn merge_value(&mut self, path: Vec, v: IValue) -> RedisResult { + fn merge_value(&mut self, path: Vec, mut v: IValue) -> RedisResult { let root = self.get_value()?.unwrap(); let updated = if path.is_empty() { // update the root - merge(root, &v); + merge(root, v); true } else { replace(&path, root, |current| { - merge(current, &v); + merge(current, v.take()); Ok(current.take()) })? }; @@ -297,38 +316,30 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn dict_add(&mut self, path: Vec, key: &str, mut v: IValue) -> RedisResult { - let mut updated = false; - if path.is_empty() { - // update the root - let root = self.get_value().unwrap().unwrap(); - if let Some(o) = root.as_object_mut() { - if !o.contains_key(key) { - updated = true; + let root = self.get_value()?.unwrap(); + let mut cb = |val: &mut IValue| { + let ret = val.as_object_mut().map_or(false, |o| { + let res = !o.contains_key(key); + if res { o.insert(key.to_string(), v.take()); } - } + res + }); + ret + }; + + let updated = if path.is_empty() { + // update the root + Some(cb(root)) } else { - update(&path, self.get_value().unwrap().unwrap(), |val| { - if val.is_object() { - let o = val.as_object_mut().unwrap(); - if !o.contains_key(key) { - updated = true; - o.insert(key.to_string(), v.take()); - } - } - Ok(Some(())) - })?; + update(&path, root, |val| Ok(cb(val)))? } + .unwrap_or(false); Ok(updated) } fn delete_path(&mut self, path: Vec) -> RedisResult { - let mut deleted = false; - update(&path, self.get_value().unwrap().unwrap(), |_v| { - deleted = true; // might delete more than a single value - Ok(None::<()>) - })?; - Ok(deleted) + remove(&path, self.get_value()?.unwrap()).map_err(Into::into) } fn incr_by(&mut self, path: Vec, num: &str) -> RedisResult { @@ -506,9 +517,9 @@ impl ReadHolder for IValueKeyHolderRead { } } -fn merge(doc: &mut IValue, patch: &IValue) { +fn merge(doc: &mut IValue, mut patch: IValue) { if !patch.is_object() { - *doc = patch.clone(); + *doc = patch; return; } @@ -516,13 +527,20 @@ fn merge(doc: &mut IValue, patch: &IValue) { *doc = IObject::new().into(); } let map = doc.as_object_mut().unwrap(); - for (key, value) in patch.as_object().unwrap() { - if value.is_null() { - map.remove(key.as_str()); - } else { - merge(map.entry(key.as_str()).or_insert(IValue::NULL), value); - } - } + patch + .as_object_mut() + .unwrap() + .into_iter() + .for_each(|(key, value)| { + if value.is_null() { + map.remove(key.as_str()); + } else { + merge( + map.entry(key.as_str()).or_insert(IValue::NULL), + value.take(), + ) + } + }) } pub struct RedisIValueJsonKeyManager<'a> { From 76379713042b84398049c39b786c738d956eab22 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Mon, 19 Aug 2024 17:49:50 +0300 Subject: [PATCH 12/33] mset is concerning --- redis_json/src/commands.rs | 153 +++++++++++++++++++----------------- redis_json/src/key_value.rs | 6 +- 2 files changed, 85 insertions(+), 74 deletions(-) diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index b509a6bb4..372755d5f 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -306,42 +306,52 @@ pub fn json_merge(manager: M, ctx: &Context, args: Vec) /// JSON.MSET [[ ]...] /// pub fn json_mset(manager: M, ctx: &Context, args: Vec) -> RedisResult { - let mut args = args.into_iter().skip(1); - - if args.len() < 3 { + if args.len() == 1 || (args.len() - 1) % 3 != 0 { return Err(RedisError::WrongArity); } // Collect all the actions from the args (redis_key, update_info, value) - let mut actions = Vec::new(); - while let Ok(key) = args.next_arg() { - let mut redis_key = manager.open_key_write(ctx, key)?; - - // Verify the key is a JSON type - let key_value = redis_key.get_value()?; - - // Verify the path is valid and get all the update info - let path = Path::new(args.next_str()?); - let update_info = if path == *JSON_ROOT_PATH { - None - } else if let Some(value) = key_value { - Some(KeyValue::new(value).find_paths(path.get_path(), SetOptions::None)?) - } else { - return Err(RedisError::Str( - "ERR new objects must be created at the root", - )); - }; + let actions: Vec<_> = args[1..] + .chunks_exact(3) + .map(|args| -> RedisResult<_> { + let [key, path, value] = args else { + unreachable!(); + }; + let mut redis_key = manager.open_key_write(ctx, key.safe_clone(ctx))?; - // Parse the input and validate it's valid JSON - let value_str = args.next_str()?; - let value = manager.from_str(value_str, Format::JSON, true)?; + // Verify the path is valid and get all the update info + let update_info = path.try_as_str().map(Path::new).and_then(|path| { + if path == *JSON_ROOT_PATH { + Ok(None) + } else { + // Verify the key is a JSON type + redis_key.get_value()?.map_or_else( + || { + Err(RedisError::Str( + "ERR new objects must be created at the root", + )) + }, + |value| { + KeyValue::new(value) + .find_paths(path.get_path(), SetOptions::None) + .into_both() + }, + ) + } + })?; - actions.push((redis_key, update_info, value)); - } + // Parse the input and validate it's valid JSON + let value = value + .try_as_str() + .and_then(|value| manager.from_str(value, Format::JSON, true).into_both())?; + + Ok((redis_key, update_info, value)) + }) + .try_collect()?; - let res = actions + actions .into_iter() - .fold(REDIS_OK, |res, (mut redis_key, update_info, value)| { + .try_for_each(|(mut redis_key, update_info, value)| { let updated = if let Some(update_info) = update_info { !update_info.is_empty() && apply_updates::(&mut redis_key, value, update_info) } else { @@ -351,11 +361,12 @@ pub fn json_mset(manager: M, ctx: &Context, args: Vec) if updated { redis_key.notify_keyspace_event(ctx, "json.mset")? } - res - }); - - manager.apply_changes(ctx); - res + Ok(()) + }) + .and_then(|_| { + manager.apply_changes(ctx); + REDIS_OK + }) } fn apply_updates(redis_key: &mut M::WriteHolder, value: M::O, ui: Vec) -> bool @@ -469,10 +480,10 @@ where /// Sort the paths so higher indices precede lower indices on the same array, /// And longer paths precede shorter paths /// And if a path is a sub-path of the other, then only paths with shallower hierarchy (closer to the top-level) remain -pub fn prepare_paths_for_updating(paths: &mut Vec>) { +pub fn prepare_paths_for_updating(mut paths: Vec>) -> Vec> { if paths.len() < 2 { // No need to reorder when there are less than 2 paths - return; + return paths; } paths.sort_by(|v1, v2| { v1.iter() @@ -520,6 +531,7 @@ pub fn prepare_paths_for_updating(paths: &mut Vec>) { .map(|found| path == *found) .unwrap_or(false) }); + paths } /// @@ -540,8 +552,8 @@ pub fn json_del(manager: M, ctx: &Context, args: Vec) - redis_key.delete()?; 1 } else { - let mut paths = find_paths(path.get_path(), doc, |_| true)?; - prepare_paths_for_updating(&mut paths); + let paths = + find_paths(path.get_path(), doc, |_| true).map(prepare_paths_for_updating)?; paths.into_iter().try_fold(0i64, |acc, p| { redis_key .delete_path(p) @@ -836,7 +848,7 @@ pub fn json_bool_toggle( ) -> RedisResult { let mut args = args.into_iter().skip(1); let key = args.next_arg()?; - let path = Path::new(args.next_str()?); + let path = args.next_str().map(Path::new)?; let redis_key = manager.open_key_write(ctx, key)?; if path.is_legacy() { @@ -1127,17 +1139,16 @@ where .ok_or_else(RedisError::nonexistent_key)?; let paths = find_all_paths(path, root, |v| v.get_type() == SelectValueType::Array)?; - let mut res = vec![]; let mut need_notify = false; - for p in paths { - res.push(match p { - Some(p) => { + let res: Vec<_> = paths + .into_iter() + .map(|p| { + p.map_or(Ok(RedisValue::Null), |p| { need_notify = true; - (redis_key.arr_append(p, args.clone())? as i64).into() - } - _ => RedisValue::Null, - }); - } + redis_key.arr_append(p, args.clone()).map(Into::into) + }) + }) + .try_collect()?; if need_notify { redis_key.notify_keyspace_event(ctx, "json.arrappend")?; manager.apply_changes(ctx); @@ -1256,17 +1267,16 @@ where let paths = find_all_paths(path, root, |v| v.get_type() == SelectValueType::Array)?; - let mut res: Vec = vec![]; let mut need_notify = false; - for p in paths { - res.push(match p { - Some(p) => { + let res: Vec<_> = paths + .into_iter() + .map(|p| { + p.map_or(Ok(RedisValue::Null), |p| { need_notify = true; - (redis_key.arr_insert(p, &args, index)? as i64).into() - } - _ => RedisValue::Null, - }); - } + redis_key.arr_insert(p, &args, index).map(Into::into) + }) + }) + .try_collect()?; if need_notify { redis_key.notify_keyspace_event(ctx, "json.arrinsert")?; @@ -1445,23 +1455,24 @@ where .ok_or_else(RedisError::nonexistent_key)?; let paths = find_all_paths(path, root, |v| v.get_type() == SelectValueType::Array)?; - let mut res: Vec = vec![]; let mut need_notify = false; - for p in paths { - res.push(match p { - Some(p) => redis_key.arr_pop(p, index, |v| { - v.map_or(Ok(RedisValue::Null), |v| { - need_notify = true; - if format_options.is_resp3_reply() { - Ok(KeyValue::value_to_resp3(v, format_options)) - } else { - serde_json::to_string(&v).into_both() - } + let res: Vec<_> = paths + .into_iter() + .map(|p| { + p.map_or(Ok(RedisValue::Null), |p| { + redis_key.arr_pop(p, index, |v| { + v.map_or(Ok(RedisValue::Null), |v| { + need_notify = true; + if format_options.is_resp3_reply() { + Ok(KeyValue::value_to_resp3(v, format_options)) + } else { + serde_json::to_string(&v).into_both() + } + }) }) - })?, - _ => RedisValue::Null, // Not an array - }); - } + }) + }) + .try_collect()?; if need_notify { redis_key.notify_keyspace_event(ctx, "json.arrpop")?; manager.apply_changes(ctx); diff --git a/redis_json/src/key_value.rs b/redis_json/src/key_value.rs index b451048b9..777f2c005 100644 --- a/redis_json/src/key_value.rs +++ b/redis_json/src/key_value.rs @@ -306,11 +306,11 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { pub fn find_paths(&mut self, path: &str, option: SetOptions) -> Result, Error> { if option != SetOptions::NotExists { let query = compile(path)?; - let mut res = calc_once_paths(query, self.val); + let mut paths = calc_once_paths(query, self.val); if option != SetOptions::MergeExisting { - prepare_paths_for_updating(&mut res); + paths = prepare_paths_for_updating(paths); } - let res = res + let res = paths .into_iter() .map(|v| UpdateInfo::SUI(SetUpdateInfo { path: v })) .collect_vec(); From a949ea8c5d9311874e3f8816788d2668c8f49386 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Mon, 19 Aug 2024 18:19:23 +0300 Subject: [PATCH 13/33] further reducing complexity --- redis_json/src/ivalue_manager.rs | 53 ++++++++++---------------------- 1 file changed, 16 insertions(+), 37 deletions(-) diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index c8b3075d8..bea1ed268 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -11,7 +11,6 @@ use crate::redisjson::{normalize_arr_start_index, ResultInto}; use crate::Format; use crate::REDIS_JSON_TYPE; use bson::{from_document, Document}; -use ijson::object::Entry; use ijson::{DestructuredMut, INumber, IObject, IString, IValue, ValueType}; use json_path::select_value::{SelectValue, SelectValueType}; use redis_module::key::{verify_type, KeyFlags, RedisKey, RedisKeyWritable}; @@ -40,9 +39,9 @@ pub struct IValueKeyHolderWrite<'a> { /// The new value is the value returned from `func`, which is called on the current value. /// If the returned value from `func` is [`Err`], the current value remains (although it could be modified by `func`) /// -fn replace(path: &[String], root: &mut IValue, mut func: F) -> Result +fn replace(path: &[String], root: &mut IValue, mut func: F) -> bool where - F: FnMut(&mut IValue) -> Result, + F: FnMut(&mut IValue) -> IValue, { let mut target = root; @@ -53,12 +52,7 @@ where let target_opt = match target_once.destructure_mut() { DestructuredMut::Object(obj) => { if is_last { - if let Entry::Occupied(mut e) = obj.entry(token) { - let v = e.get_mut(); - *v = func(v)?; - return Ok(true); - } - return Ok(false); + return obj.get_mut(token.as_str()).map(|v| *v = func(v)).is_some(); } obj.get_mut(token.as_str()) } @@ -68,12 +62,7 @@ where arr, token )); if is_last { - if idx < arr.len() { - let v = &mut arr.as_mut_slice()[idx]; - *v = func(v)?; - return Ok(true); - } - return Ok(false); + return arr.get_mut(idx).map(|v| *v = func(v)).is_some(); } arr.get_mut(idx) } @@ -87,7 +76,7 @@ where } } - Ok(false) + false } /// @@ -96,7 +85,7 @@ where /// The value is modified by `func`, which is called on the current value. /// If the returned value from `func` is [`Err`], the current value remains (although it could be modified by `func`) /// -fn update(path: &[String], root: &mut IValue, mut func: F) -> Result, Error> +fn update(path: &[String], root: &mut IValue, func: F) -> Result, Error> where F: FnMut(&mut IValue) -> Result, { @@ -109,12 +98,7 @@ where let target_opt = match target_once.destructure_mut() { DestructuredMut::Object(obj) => { if is_last { - if let Entry::Occupied(mut e) = obj.entry(token) { - let v = e.get_mut(); - return func(v).map(Some); - } else { - return Ok(None); - } + return obj.get_mut(token.as_str()).map(func).transpose(); } obj.get_mut(token.as_str()) } @@ -124,12 +108,7 @@ where arr, token )); if is_last { - if idx < arr.len() { - let v = &mut arr.as_mut_slice()[idx]; - return func(v).map(Some); - } else { - return Ok(None); - } + return arr.get_mut(idx).map(func).transpose(); } arr.get_mut(idx) } @@ -149,7 +128,7 @@ where /// /// Removes a value at a given `path`, starting from `root` /// -fn remove(path: &[String], root: &mut IValue) -> Result { +fn remove(path: &[String], root: &mut IValue) -> bool { let mut target = root; let last_index = path.len().saturating_sub(1); @@ -159,7 +138,7 @@ fn remove(path: &[String], root: &mut IValue) -> Result { let target_opt = match target_once.destructure_mut() { DestructuredMut::Object(obj) => { if is_last { - return Ok(obj.remove(token.as_str()).is_some()); + return obj.remove(token.as_str()).is_some(); } obj.get_mut(token.as_str()) } @@ -169,7 +148,7 @@ fn remove(path: &[String], root: &mut IValue) -> Result { arr, token )); if is_last { - return Ok(arr.remove(idx).is_some()); + return arr.remove(idx).is_some(); } arr.get_mut(idx) } @@ -183,7 +162,7 @@ fn remove(path: &[String], root: &mut IValue) -> Result { } } - Ok(false) + false } impl<'a> IValueKeyHolderWrite<'a> { @@ -296,7 +275,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { // update the root self.set_root(Some(v)).and(Ok(true)) } else { - replace(&path, self.get_value()?.unwrap(), |_| Ok(v.take())).map_err(Into::into) + Ok(replace(&path, self.get_value()?.unwrap(), |_| v.take())) } } @@ -309,8 +288,8 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } else { replace(&path, root, |current| { merge(current, v.take()); - Ok(current.take()) - })? + current.take() + }) }; Ok(updated) } @@ -339,7 +318,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn delete_path(&mut self, path: Vec) -> RedisResult { - remove(&path, self.get_value()?.unwrap()).map_err(Into::into) + Ok(remove(&path, self.get_value()?.unwrap())) } fn incr_by(&mut self, path: Vec, num: &str) -> RedisResult { From 3e576d48d41b5f3973fbf64e6f1b516248cb8cc9 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Tue, 20 Aug 2024 13:34:56 +0300 Subject: [PATCH 14/33] work --- redis_json/src/ivalue_manager.rs | 192 +++++++++++-------------------- redis_json/src/manager.rs | 9 +- 2 files changed, 68 insertions(+), 133 deletions(-) diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index bea1ed268..23c4eaa7d 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -43,40 +43,20 @@ fn replace(path: &[String], root: &mut IValue, mut func: F) -> bool where F: FnMut(&mut IValue) -> IValue, { - let mut target = root; - - let last_index = path.len().saturating_sub(1); - for (i, token) in path.iter().enumerate() { - let target_once = target; - let is_last = i == last_index; - let target_opt = match target_once.destructure_mut() { - DestructuredMut::Object(obj) => { - if is_last { - return obj.get_mut(token.as_str()).map(|v| *v = func(v)).is_some(); - } - obj.get_mut(token.as_str()) - } + path.iter() + .try_fold(root, |target, token| match target.destructure_mut() { + DestructuredMut::Object(obj) => obj.get_mut(token.as_str()), DestructuredMut::Array(arr) => { let idx = token.parse::().expect(&format!( "An array index is parsed successfully. Array = {:?}, index = {:?}", arr, token )); - if is_last { - return arr.get_mut(idx).map(|v| *v = func(v)).is_some(); - } arr.get_mut(idx) } _ => None, - }; - - if let Some(t) = target_opt { - target = t; - } else { - break; - } - } - - false + }) + .map(|v| *v = func(v)) + .is_some() } /// @@ -89,80 +69,54 @@ fn update(path: &[String], root: &mut IValue, func: F) -> Result where F: FnMut(&mut IValue) -> Result, { - let mut target = root; - - let last_index = path.len().saturating_sub(1); - for (i, token) in path.iter().enumerate() { - let target_once = target; - let is_last = i == last_index; - let target_opt = match target_once.destructure_mut() { - DestructuredMut::Object(obj) => { - if is_last { - return obj.get_mut(token.as_str()).map(func).transpose(); - } - obj.get_mut(token.as_str()) - } + path.iter() + .try_fold(root, |target, token| match target.destructure_mut() { + DestructuredMut::Object(obj) => obj.get_mut(token.as_str()), DestructuredMut::Array(arr) => { let idx = token.parse::().expect(&format!( "An array index is parsed successfully. Array = {:?}, index = {:?}", arr, token )); - if is_last { - return arr.get_mut(idx).map(func).transpose(); - } arr.get_mut(idx) } _ => None, - }; - - if let Some(t) = target_opt { - target = t; - } else { - break; - } - } - - Ok(None) + }) + .map(func) + .transpose() } /// /// Removes a value at a given `path`, starting from `root` /// fn remove(path: &[String], root: &mut IValue) -> bool { - let mut target = root; - - let last_index = path.len().saturating_sub(1); - for (i, token) in path.iter().enumerate() { - let target_once = target; - let is_last = i == last_index; - let target_opt = match target_once.destructure_mut() { - DestructuredMut::Object(obj) => { - if is_last { - return obj.remove(token.as_str()).is_some(); - } - obj.get_mut(token.as_str()) - } + path[..path.len() - 1] + .iter() + .try_fold(root, |target, token| match target.destructure_mut() { + DestructuredMut::Object(obj) => obj.get_mut(token.as_str()), DestructuredMut::Array(arr) => { let idx = token.parse::().expect(&format!( "An array index is parsed successfully. Array = {:?}, index = {:?}", arr, token )); - if is_last { - return arr.remove(idx).is_some(); - } arr.get_mut(idx) } _ => None, - }; - - if let Some(t) = target_opt { - target = t; - } else { - break; - } - } - - false + }) + .and_then(|target| { + let token = &path[path.len() - 1]; + match target.destructure_mut() { + DestructuredMut::Object(obj) => obj.remove(token.as_str()), + DestructuredMut::Array(arr) => { + let idx = token.parse::().expect(&format!( + "An array index is parsed successfully. Array = {:?}, index = {:?}", + arr, token + )); + arr.remove(idx) + } + _ => None, + } + }) + .is_some() } impl<'a> IValueKeyHolderWrite<'a> { @@ -173,10 +127,11 @@ impl<'a> IValueKeyHolderWrite<'a> { let root = self.get_value()?.unwrap(); if paths.is_empty() { // updating the root require special treatment - op_fun(root).into_both() + op_fun(root).map(Some) } else { - update(paths, root, op_fun).into_both() + update(paths, root, op_fun) } + .map_err(Into::into) } fn do_num_op( @@ -295,8 +250,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn dict_add(&mut self, path: Vec, key: &str, mut v: IValue) -> RedisResult { - let root = self.get_value()?.unwrap(); - let mut cb = |val: &mut IValue| { + self.do_op(&path, |val| { let ret = val.as_object_mut().map_or(false, |o| { let res = !o.contains_key(key); if res { @@ -304,17 +258,10 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } res }); - ret - }; - - let updated = if path.is_empty() { - // update the root - Some(cb(root)) - } else { - update(&path, root, |val| Ok(cb(val)))? - } - .unwrap_or(false); - Ok(updated) + Ok(ret) + }) + .map(|o| o.unwrap_or(false)) + .into_both() } fn delete_path(&mut self, path: Vec) -> RedisResult { @@ -352,14 +299,13 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { fn str_append(&mut self, path: Vec, val: String) -> RedisResult { let json = serde_json::from_str(&val)?; if let serde_json::Value::String(s) = json { - let res = self.do_op(&path, |v| { + self.do_op(&path, |v| { let v_str = v.as_string_mut().unwrap(); let new_str = [v_str.as_str(), s.as_str()].concat(); *v_str = IString::intern(&new_str); Ok(new_str.len()) - })?; - - res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) + }) + .and_then(|res| res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist()))) } else { Err(RedisError::String(err_msg_json_expected( "string", @@ -369,15 +315,14 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn arr_append(&mut self, path: Vec, args: Vec) -> RedisResult { - let res = self.do_op(&path, |v| { + self.do_op(&path, |v| { let arr = v.as_array_mut().unwrap(); for a in &args { arr.push(a.clone()); } Ok(arr.len()) - })?; - - res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) + }) + .and_then(|res| res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist()))) } fn arr_insert( @@ -386,7 +331,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { args: &[IValue], index: i64, ) -> RedisResult { - let res = self.do_op(&paths, |v: &mut IValue| { + self.do_op(&paths, |v| { // Verify legal index in bounds let len = v.len().unwrap() as i64; let index = if index < 0 { len + index } else { index }; @@ -401,17 +346,14 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { index += 1; } Ok(curr.len()) - })?; - - res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) + }) + .and_then(|res| res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist()))) } - fn arr_pop) -> RedisResult>( - &mut self, - path: Vec, - index: i64, - serialize_callback: C, - ) -> RedisResult { + fn arr_pop(&mut self, path: Vec, index: i64, serialize_callback: C) -> RedisResult + where + C: FnOnce(Option<&IValue>) -> RedisResult, + { let res = self .do_op(&path, |v| { if let Some(array) = v.as_array_mut() { @@ -431,7 +373,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn arr_trim(&mut self, path: Vec, start: i64, stop: i64) -> RedisResult { - let res = self.do_op(&path, |v| { + self.do_op(&path, |v| { if let Some(array) = v.as_array_mut() { let len = array.len() as i64; let stop = stop.normalize(len); @@ -452,31 +394,27 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } else { Err(err_json(v, "array")) } - })?; - - res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) + }) + .and_then(|res| res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist()))) } fn clear(&mut self, path: Vec) -> RedisResult { let cleared = self - .do_op(&path, |v| match v.type_() { - ValueType::Object => { - let obj = v.as_object_mut().unwrap(); + .do_op(&path, |v| match v.destructure_mut() { + DestructuredMut::Object(obj) => { obj.clear(); - Ok(Some(1)) + Ok(1) } - ValueType::Array => { - let arr = v.as_array_mut().unwrap(); + DestructuredMut::Array(arr) => { arr.clear(); - Ok(Some(1)) + Ok(1) } - ValueType::Number => { - *v = IValue::from(0); - Ok(Some(1)) + DestructuredMut::Number(n) => { + *n = INumber::from(0); + Ok(1) } - _ => Ok(None), + _ => Ok(0), })? - .flatten() .unwrap_or(0); Ok(cleared) } diff --git a/redis_json/src/manager.rs b/redis_json/src/manager.rs index dd8b54b4a..fb174f8e7 100644 --- a/redis_json/src/manager.rs +++ b/redis_json/src/manager.rs @@ -49,12 +49,9 @@ pub trait WriteHolder { fn str_append(&mut self, path: Vec, val: String) -> RedisResult; fn arr_append(&mut self, path: Vec, args: Vec) -> RedisResult; fn arr_insert(&mut self, path: Vec, args: &[O], index: i64) -> RedisResult; - fn arr_pop) -> RedisResult>( - &mut self, - path: Vec, - index: i64, - serialize_callback: C, - ) -> RedisResult; + fn arr_pop(&mut self, path: Vec, index: i64, serialize_callback: C) -> RedisResult + where + C: FnOnce(Option<&V>) -> RedisResult; fn arr_trim(&mut self, path: Vec, start: i64, stop: i64) -> RedisResult; fn clear(&mut self, path: Vec) -> RedisResult; fn notify_keyspace_event(self, ctx: &Context, command: &str) -> RedisResult<()>; From 38b2211ef5e8c8db04c5ad17b562dc22b06b7d13 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Tue, 20 Aug 2024 14:55:40 +0300 Subject: [PATCH 15/33] simplify error handling --- redis_json/src/ivalue_manager.rs | 173 ++++++++++++++----------------- 1 file changed, 75 insertions(+), 98 deletions(-) diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index 23c4eaa7d..3405d634a 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -7,7 +7,7 @@ use crate::error::Error; use crate::manager::{err_json, err_msg_json_expected, err_msg_json_path_doesnt_exist}; use crate::manager::{Manager, ReadHolder, WriteHolder}; -use crate::redisjson::{normalize_arr_start_index, ResultInto}; +use crate::redisjson::normalize_arr_start_index; use crate::Format; use crate::REDIS_JSON_TYPE; use bson::{from_document, Document}; @@ -65,7 +65,7 @@ where /// The value is modified by `func`, which is called on the current value. /// If the returned value from `func` is [`Err`], the current value remains (although it could be modified by `func`) /// -fn update(path: &[String], root: &mut IValue, func: F) -> Result, Error> +fn update(path: &[String], root: &mut IValue, mut func: F) -> RedisResult where F: FnMut(&mut IValue) -> Result, { @@ -81,8 +81,8 @@ where } _ => None, }) - .map(func) - .transpose() + .map(|v| func(v).map_err(Into::into)) + .unwrap_or_else(|| Err(RedisError::String(err_msg_json_path_doesnt_exist()))) } /// @@ -120,18 +120,12 @@ fn remove(path: &[String], root: &mut IValue) -> bool { } impl<'a> IValueKeyHolderWrite<'a> { - fn do_op(&mut self, paths: &[String], mut op_fun: F) -> RedisResult> + fn do_op(&mut self, paths: &[String], op_fun: F) -> RedisResult where F: FnMut(&mut IValue) -> Result, { let root = self.get_value()?.unwrap(); - if paths.is_empty() { - // updating the root require special treatment - op_fun(root).map(Some) - } else { - update(paths, root, op_fun) - } - .map_err(Into::into) + update(paths, root, op_fun) } fn do_num_op( @@ -147,7 +141,7 @@ impl<'a> IValueKeyHolderWrite<'a> { { let in_value = &serde_json::from_str(num)?; if let serde_json::Value::Number(in_value) = in_value { - let res = self.do_op(&path, |v| { + self.do_op(&path, |v| { let num_res = match (v.get_type(), in_value.as_i64()) { (SelectValueType::Long, Some(num2)) => { let num1 = v.get_long(); @@ -164,21 +158,18 @@ impl<'a> IValueKeyHolderWrite<'a> { let new_val = IValue::from(num_res?); *v = new_val.clone(); Ok(new_val) - })?; - res.map_or_else( - || Err(RedisError::String(err_msg_json_path_doesnt_exist())), - |n| { - n.as_number() - .and_then(|n| { - if n.has_decimal_point() { - n.to_f64().and_then(serde_json::Number::from_f64) - } else { - n.to_i64().map(Into::into) - } - }) - .ok_or_else(|| RedisError::Str("result is not a number")) - }, - ) + }) + .and_then(|n| { + n.as_number() + .and_then(|n| { + if n.has_decimal_point() { + n.to_f64().and_then(serde_json::Number::from_f64) + } else { + n.to_i64().map(Into::into) + } + }) + .ok_or_else(|| RedisError::Str("result is not a number")) + }) } else { Err(RedisError::Str("bad input number")) } @@ -251,17 +242,14 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { fn dict_add(&mut self, path: Vec, key: &str, mut v: IValue) -> RedisResult { self.do_op(&path, |val| { - let ret = val.as_object_mut().map_or(false, |o| { + val.as_object_mut().map_or(Ok(false), |o| { let res = !o.contains_key(key); if res { o.insert(key.to_string(), v.take()); } - res - }); - Ok(ret) + Ok(res) + }) }) - .map(|o| o.unwrap_or(false)) - .into_both() } fn delete_path(&mut self, path: Vec) -> RedisResult { @@ -281,19 +269,16 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn bool_toggle(&mut self, path: Vec) -> RedisResult { - let res = self - .do_op(&path, |v| { - if let DestructuredMut::Bool(mut bool_mut) = v.destructure_mut() { - //Using DestructuredMut in order to modify a `Bool` variant - let val = bool_mut.get() ^ true; - bool_mut.set(val); - Ok(Some(val)) - } else { - Ok(None) - } - })? - .flatten(); - res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist())) + self.do_op(&path, |v| { + if let DestructuredMut::Bool(mut bool_mut) = v.destructure_mut() { + //Using DestructuredMut in order to modify a `Bool` variant + let val = bool_mut.get() ^ true; + bool_mut.set(val); + Ok(val) + } else { + Err(err_json(v, "bool")) + } + }) } fn str_append(&mut self, path: Vec, val: String) -> RedisResult { @@ -305,7 +290,6 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { *v_str = IString::intern(&new_str); Ok(new_str.len()) }) - .and_then(|res| res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist()))) } else { Err(RedisError::String(err_msg_json_expected( "string", @@ -322,7 +306,6 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } Ok(arr.len()) }) - .and_then(|res| res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist()))) } fn arr_insert( @@ -347,75 +330,69 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } Ok(curr.len()) }) - .and_then(|res| res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist()))) } fn arr_pop(&mut self, path: Vec, index: i64, serialize_callback: C) -> RedisResult where C: FnOnce(Option<&IValue>) -> RedisResult, { - let res = self - .do_op(&path, |v| { - if let Some(array) = v.as_array_mut() { + self.do_op(&path, |v| { + v.as_array_mut() + .map(|array| { if array.is_empty() { - return Ok(None); + return None; } // Verify legal index in bounds let len = array.len() as i64; let index = normalize_arr_start_index(index, len) as usize; - Ok(array.remove(index)) - } else { - Err(err_json(v, "array")) - } - })? - .flatten(); - serialize_callback(res.as_ref()) + array.remove(index) + }) + .ok_or_else(|| err_json(v, "array")) + }) + .and_then(|res| serialize_callback(res.as_ref())) } fn arr_trim(&mut self, path: Vec, start: i64, stop: i64) -> RedisResult { self.do_op(&path, |v| { - if let Some(array) = v.as_array_mut() { - let len = array.len() as i64; - let stop = stop.normalize(len); - let start = if start < 0 || start < len { - start.normalize(len) - } else { - stop + 1 // start >=0 && start >= len - }; - let range = if start > stop || len == 0 { - 0..0 // Return an empty array - } else { - start..(stop + 1) - }; + v.as_array_mut() + .map(|array| { + let len = array.len() as i64; + let stop = stop.normalize(len); + let start = if start < 0 || start < len { + start.normalize(len) + } else { + stop + 1 // start >=0 && start >= len + }; + let range = if start > stop || len == 0 { + 0..0 // Return an empty array + } else { + start..(stop + 1) + }; - array.rotate_left(range.start); - array.truncate(range.end - range.start); - Ok(array.len()) - } else { - Err(err_json(v, "array")) - } + array.rotate_left(range.start); + array.truncate(range.end - range.start); + array.len() + }) + .ok_or_else(|| err_json(v, "array")) }) - .and_then(|res| res.ok_or_else(|| RedisError::String(err_msg_json_path_doesnt_exist()))) } fn clear(&mut self, path: Vec) -> RedisResult { - let cleared = self - .do_op(&path, |v| match v.destructure_mut() { - DestructuredMut::Object(obj) => { - obj.clear(); - Ok(1) - } - DestructuredMut::Array(arr) => { - arr.clear(); - Ok(1) - } - DestructuredMut::Number(n) => { - *n = INumber::from(0); - Ok(1) - } - _ => Ok(0), - })? - .unwrap_or(0); + let cleared = self.do_op(&path, |v| match v.destructure_mut() { + DestructuredMut::Object(obj) => { + obj.clear(); + Ok(1) + } + DestructuredMut::Array(arr) => { + arr.clear(); + Ok(1) + } + DestructuredMut::Number(n) => { + *n = INumber::from(0); + Ok(1) + } + _ => Ok(0), + })?; Ok(cleared) } } From 4e1d1805210a1c2350fd44b823b5b434a6a811b5 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Tue, 20 Aug 2024 15:18:04 +0300 Subject: [PATCH 16/33] . --- redis_json/src/backward.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/redis_json/src/backward.rs b/redis_json/src/backward.rs index f21f737d1..6e4e85315 100644 --- a/redis_json/src/backward.rs +++ b/redis_json/src/backward.rs @@ -53,13 +53,13 @@ pub fn json_rdb_load(rdb: *mut raw::RedisModuleIO) -> Result { } NodeType::Integer => { let n = raw::load_signed(rdb)?; - Ok(Value::Number(n.into())) + Ok(n.into()) } NodeType::Number => { let n = raw::load_double(rdb)?; Number::from_f64(n) + .map(Into::into) .ok_or_else(|| Error::from("Can't load as float")) - .into_both() } NodeType::String => { let buffer = raw::load_string_buffer(rdb)?; @@ -67,7 +67,7 @@ pub fn json_rdb_load(rdb: *mut raw::RedisModuleIO) -> Result { } NodeType::Dict => { let len = raw::load_unsigned(rdb)?; - let m = (0..len) + (0..len) .map(|_| match raw::load_unsigned(rdb)?.into() { NodeType::KeyVal => { let buffer = raw::load_string_buffer(rdb)?; @@ -75,13 +75,15 @@ pub fn json_rdb_load(rdb: *mut raw::RedisModuleIO) -> Result { } _ => Err(Error::from("Can't load old RedisJSON RDB")), }) - .try_collect()?; - Ok(Value::Object(m)) + .try_collect() + .map(Value::Object) } NodeType::Array => { let len = raw::load_unsigned(rdb)?; - let v = (0..len).map(|_| json_rdb_load(rdb)).try_collect()?; - Ok(Value::Array(v)) + (0..len) + .map(|_| json_rdb_load(rdb)) + .try_collect() + .map(Value::Array) } NodeType::KeyVal => Err(Error::from("Can't load old RedisJSON RDB")), } From 59af235c2e9a38827d68818f839a10c2151ac3fa Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Tue, 20 Aug 2024 16:04:12 +0300 Subject: [PATCH 17/33] code dupe --- redis_json/src/commands.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index 372755d5f..1088cc65a 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -593,23 +593,21 @@ pub fn json_mget(manager: M, ctx: &Context, args: Vec) .map(|key| { manager .open_key_read(ctx, key) - .map_or(RedisValue::Null, |json_key| { - json_key - .get_value() + .ok() + .and_then(|json_key| { + json_key.get_value().ok().flatten().and_then(|doc| { + let key_value = KeyValue::new(doc); + let format_options = + ReplyFormatOptions::new(is_resp3(ctx), ReplyFormat::STRING); + if !path.is_legacy() { + key_value.to_string_multi(path.get_path(), format_options) + } else { + key_value.to_string_single(path.get_path(), format_options) + } .ok() - .flatten() - .map_or(RedisValue::Null, |doc| { - let key_value = KeyValue::new(doc); - let format_options = - ReplyFormatOptions::new(is_resp3(ctx), ReplyFormat::STRING); - if !path.is_legacy() { - key_value.to_string_multi(path.get_path(), format_options) - } else { - key_value.to_string_single(path.get_path(), format_options) - } - .map_or(RedisValue::Null, |v| v.into()) - }) + }) }) + .map_or(RedisValue::Null, Into::into) }) .collect_vec(); From 21779ba9a31a1f6b37cb113daf77ee0a7370fc37 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Tue, 20 Aug 2024 16:06:57 +0300 Subject: [PATCH 18/33] hoist loop invariant --- redis_json/src/commands.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index 1088cc65a..e4e2f9806 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -588,6 +588,8 @@ pub fn json_mget(manager: M, ctx: &Context, args: Vec) return Err(RedisError::WrongArity); } + let format_options = ReplyFormatOptions::new(is_resp3(ctx), ReplyFormat::STRING); + let results = keys .into_iter() .map(|key| { @@ -597,8 +599,6 @@ pub fn json_mget(manager: M, ctx: &Context, args: Vec) .and_then(|json_key| { json_key.get_value().ok().flatten().and_then(|doc| { let key_value = KeyValue::new(doc); - let format_options = - ReplyFormatOptions::new(is_resp3(ctx), ReplyFormat::STRING); if !path.is_legacy() { key_value.to_string_multi(path.get_path(), format_options) } else { From 3889230665cfab473c8fd676cc478cd4f258807a Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Tue, 20 Aug 2024 17:12:36 +0300 Subject: [PATCH 19/33] ownership --- redis_json/src/commands.rs | 289 +++++++++++-------------------- redis_json/src/ivalue_manager.rs | 32 ++-- redis_json/src/key_value.rs | 4 +- 3 files changed, 117 insertions(+), 208 deletions(-) diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index e4e2f9806..4240d013b 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -409,10 +409,10 @@ where } /// Returns tuples of Value and its concrete path which match the given `path` -fn get_all_values_and_paths<'a, T: SelectValue>( - path: &str, - doc: &'a T, -) -> RedisResult)>> { +fn get_all_values_and_paths<'a, T>(path: &str, doc: &'a T) -> RedisResult)>> +where + T: SelectValue, +{ let query = compile(path).map_err(|e| RedisError::String(e.to_string()))?; let res = calc_once_with_paths(query, doc) .into_iter() @@ -443,24 +443,18 @@ where .collect() } -fn find_all_paths( - path: &str, - doc: &T, - f: F, -) -> RedisResult>>> +fn find_all_paths(path: &str, doc: &T, f: F) -> RedisResult>>> where + T: SelectValue, F: Fn(&T) -> bool, { let res = get_all_values_and_paths(path, doc)?; Ok(filter_paths(res, f)) } -fn find_all_values<'a, T: SelectValue, F>( - path: &str, - doc: &'a T, - f: F, -) -> RedisResult>> +fn find_all_values<'a, T, F>(path: &str, doc: &'a T, f: F) -> RedisResult>> where + T: SelectValue, F: Fn(&T) -> bool, { let res = get_all_values_and_paths(path, doc)?; @@ -638,10 +632,7 @@ pub fn json_type(manager: M, ctx: &Context, args: Vec) } } -fn json_type_impl(redis_key: &M::ReadHolder, path: &str) -> RedisResult -where - M: Manager, -{ +fn json_type_impl(redis_key: &M::ReadHolder, path: &str) -> RedisResult { redis_key.get_value()?.map_or(Ok(RedisValue::Null), |root| { let value = KeyValue::new(root) .get_values(path)? @@ -653,10 +644,7 @@ where }) } -fn json_type_legacy(redis_key: &M::ReadHolder, path: &str) -> RedisResult -where - M: Manager, -{ +fn json_type_legacy(redis_key: &M::ReadHolder, path: &str) -> RedisResult { let value = redis_key .get_value()? .map(|doc| KeyValue::new(doc).get_type(path).map(|s| s.into()).ok()) @@ -671,16 +659,13 @@ enum NumOp { Pow, } -fn json_num_op( +fn json_num_op( manager: M, ctx: &Context, args: Vec, cmd: &str, op: NumOp, -) -> RedisResult -where - M: Manager, -{ +) -> RedisResult { let mut args = args.into_iter().skip(1); let key = args.next_arg()?; @@ -717,7 +702,7 @@ where } } -fn json_num_op_impl( +fn json_num_op_impl( manager: M, mut redis_key: M::WriteHolder, ctx: &Context, @@ -725,10 +710,7 @@ fn json_num_op_impl( number: &str, op: NumOp, cmd: &str, -) -> RedisResult>> -where - M: Manager, -{ +) -> RedisResult>> { let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; @@ -761,7 +743,7 @@ where Ok(res) } -fn json_num_op_legacy( +fn json_num_op_legacy( manager: M, mut redis_key: M::WriteHolder, ctx: &Context, @@ -769,10 +751,7 @@ fn json_num_op_legacy( number: &str, op: NumOp, cmd: &str, -) -> RedisResult -where - M: Manager, -{ +) -> RedisResult { let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; @@ -806,44 +785,40 @@ where /// /// JSON.NUMINCRBY /// -pub fn json_num_incrby( - manager: M, - ctx: &Context, - args: Vec, -) -> RedisResult { +pub fn json_num_incrby(manager: M, ctx: &Context, args: Vec) -> RedisResult +where + M: Manager, +{ json_num_op(manager, ctx, args, "json.numincrby", NumOp::Incr) } /// /// JSON.NUMMULTBY /// -pub fn json_num_multby( - manager: M, - ctx: &Context, - args: Vec, -) -> RedisResult { +pub fn json_num_multby(manager: M, ctx: &Context, args: Vec) -> RedisResult +where + M: Manager, +{ json_num_op(manager, ctx, args, "json.nummultby", NumOp::Mult) } /// /// JSON.NUMPOWBY /// -pub fn json_num_powby( - manager: M, - ctx: &Context, - args: Vec, -) -> RedisResult { +pub fn json_num_powby(manager: M, ctx: &Context, args: Vec) -> RedisResult +where + M: Manager, +{ json_num_op(manager, ctx, args, "json.numpowby", NumOp::Pow) } // /// JSON.TOGGLE /// -pub fn json_bool_toggle( - manager: M, - ctx: &Context, - args: Vec, -) -> RedisResult { +pub fn json_bool_toggle(manager: M, ctx: &Context, args: Vec) -> RedisResult +where + M: Manager, +{ let mut args = args.into_iter().skip(1); let key = args.next_arg()?; let path = args.next_str().map(Path::new)?; @@ -856,15 +831,12 @@ pub fn json_bool_toggle( } } -fn json_bool_toggle_impl( +fn json_bool_toggle_impl( manager: M, mut redis_key: M::WriteHolder, ctx: &Context, path: &str, -) -> RedisResult -where - M: Manager, -{ +) -> RedisResult { let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; @@ -886,15 +858,12 @@ where Ok(res.into()) } -fn json_bool_toggle_legacy( +fn json_bool_toggle_legacy( manager: M, mut redis_key: M::WriteHolder, ctx: &Context, path: &str, -) -> RedisResult -where - M: Manager, -{ +) -> RedisResult { let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; @@ -917,11 +886,10 @@ where /// /// JSON.STRAPPEND [path] /// -pub fn json_str_append( - manager: M, - ctx: &Context, - args: Vec, -) -> RedisResult { +pub fn json_str_append(manager: M, ctx: &Context, args: Vec) -> RedisResult +where + M: Manager, +{ let mut args = args.into_iter().skip(1); let key = args.next_arg()?; @@ -948,16 +916,13 @@ pub fn json_str_append( } } -fn json_str_append_impl( +fn json_str_append_impl( manager: M, mut redis_key: M::WriteHolder, ctx: &Context, path: &str, json: &str, -) -> RedisResult -where - M: Manager, -{ +) -> RedisResult { let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; @@ -981,16 +946,13 @@ where Ok(res.into()) } -fn json_str_append_legacy( +fn json_str_append_legacy( manager: M, mut redis_key: M::WriteHolder, ctx: &Context, path: &str, json: &str, -) -> RedisResult -where - M: Manager, -{ +) -> RedisResult { let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; @@ -1028,10 +990,7 @@ pub fn json_str_len(manager: M, ctx: &Context, args: Vec(redis_key: &M::ReadHolder, path: &str) -> RedisResult -where - M: Manager, -{ +fn json_str_len_impl(redis_key: &M::ReadHolder, path: &str) -> RedisResult { let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; @@ -1043,10 +1002,7 @@ where Ok(res.into()) } -fn json_str_len_legacy(redis_key: &M::ReadHolder, path: &str) -> RedisResult -where - M: Manager, -{ +fn json_str_len_legacy(redis_key: &M::ReadHolder, path: &str) -> RedisResult { match redis_key.get_value()? { Some(doc) => Ok(RedisValue::Integer(KeyValue::new(doc).str_len(path)? as i64)), None => Ok(RedisValue::Null), @@ -1056,11 +1012,10 @@ where /// /// JSON.ARRAPPEND [json ...] /// -pub fn json_arr_append( - manager: M, - ctx: &Context, - args: Vec, -) -> RedisResult { +pub fn json_arr_append(manager: M, ctx: &Context, args: Vec) -> RedisResult +where + M: Manager, +{ let mut args = args.into_iter().skip(1).peekable(); let key = args.next_arg()?; @@ -1080,22 +1035,19 @@ pub fn json_arr_append( let redis_key = manager.open_key_write(ctx, key)?; if path.is_legacy() { - json_arr_append_legacy::(manager, redis_key, ctx, &path, args) + json_arr_append_legacy::(manager, redis_key, ctx, path, args) } else { json_arr_append_impl::(manager, redis_key, ctx, path.get_path(), args) } } -fn json_arr_append_legacy( +fn json_arr_append_legacy( manager: M, mut redis_key: M::WriteHolder, ctx: &Context, - path: &Path, + path: Path, args: Vec, -) -> RedisResult -where - M: Manager, -{ +) -> RedisResult { let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; @@ -1122,16 +1074,13 @@ where } } -fn json_arr_append_impl( +fn json_arr_append_impl( manager: M, mut redis_key: M::WriteHolder, ctx: &Context, path: &str, args: Vec, -) -> RedisResult -where - M: Manager, -{ +) -> RedisResult { let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; @@ -1179,11 +1128,10 @@ pub enum ObjectLen { /// /// JSON.ARRINDEX [start [stop]] /// -pub fn json_arr_index( - manager: M, - ctx: &Context, - args: Vec, -) -> RedisResult { +pub fn json_arr_index(manager: M, ctx: &Context, args: Vec) -> RedisResult +where + M: Manager, +{ let mut args = args.into_iter().skip(1); let key = args.next_arg()?; @@ -1219,11 +1167,10 @@ pub fn json_arr_index( /// /// JSON.ARRINSERT [json ...] /// -pub fn json_arr_insert( - manager: M, - ctx: &Context, - args: Vec, -) -> RedisResult { +pub fn json_arr_insert(manager: M, ctx: &Context, args: Vec) -> RedisResult +where + M: Manager, +{ let mut args = args.into_iter().skip(1).peekable(); let key = args.next_arg()?; @@ -1248,17 +1195,14 @@ pub fn json_arr_insert( } } -fn json_arr_insert_impl( +fn json_arr_insert_impl( manager: M, mut redis_key: M::WriteHolder, ctx: &Context, path: &str, index: i64, args: Vec, -) -> RedisResult -where - M: Manager, -{ +) -> RedisResult { let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; @@ -1283,17 +1227,14 @@ where Ok(res.into()) } -fn json_arr_insert_legacy( +fn json_arr_insert_legacy( manager: M, mut redis_key: M::WriteHolder, ctx: &Context, path: &str, index: i64, args: Vec, -) -> RedisResult -where - M: Manager, -{ +) -> RedisResult { let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; @@ -1328,10 +1269,7 @@ pub fn json_arr_len(manager: M, ctx: &Context, args: Vec(key: M::ReadHolder, path: Path) -> RedisResult -where - M: Manager, -{ +fn json_arr_len_impl(key: M::ReadHolder, path: Path) -> RedisResult { let root = key.get_value()?.ok_or(RedisError::nonexistent_key())?; let values = find_all_values(path.get_path(), root, |v| { v.get_type() == SelectValueType::Array @@ -1343,10 +1281,7 @@ where Ok(res.into()) } -fn json_arr_len_legacy(key: M::ReadHolder, path: Path) -> RedisResult -where - M: Manager, -{ +fn json_arr_len_legacy(key: M::ReadHolder, path: Path) -> RedisResult { let root = match key.get_value()? { Some(k) => k, None => { @@ -1437,17 +1372,14 @@ pub fn json_arr_pop(manager: M, ctx: &Context, args: Vec( +fn json_arr_pop_impl( manager: M, mut redis_key: M::WriteHolder, ctx: &Context, path: &str, index: i64, format_options: ReplyFormatOptions, -) -> RedisResult -where - M: Manager, -{ +) -> RedisResult { let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; @@ -1478,16 +1410,13 @@ where Ok(res.into()) } -fn json_arr_pop_legacy( +fn json_arr_pop_legacy( manager: M, mut redis_key: M::WriteHolder, ctx: &Context, path: &str, index: i64, -) -> RedisResult -where - M: Manager, -{ +) -> RedisResult { let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; @@ -1530,17 +1459,14 @@ pub fn json_arr_trim(manager: M, ctx: &Context, args: Vec(manager, redis_key, ctx, path.get_path(), start, stop) } } -fn json_arr_trim_impl( +fn json_arr_trim_impl( manager: M, mut redis_key: M::WriteHolder, ctx: &Context, path: &str, start: i64, stop: i64, -) -> RedisResult -where - M: Manager, -{ +) -> RedisResult { let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; @@ -1565,17 +1491,14 @@ where Ok(res.into()) } -fn json_arr_trim_legacy( +fn json_arr_trim_legacy( manager: M, mut redis_key: M::WriteHolder, ctx: &Context, path: &str, start: i64, stop: i64, -) -> RedisResult -where - M: Manager, -{ +) -> RedisResult { let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; @@ -1611,10 +1534,7 @@ pub fn json_obj_keys(manager: M, ctx: &Context, args: Vec(redis_key: M::ReadHolder, path: &str) -> RedisResult -where - M: Manager, -{ +fn json_obj_keys_impl(redis_key: M::ReadHolder, path: &str) -> RedisResult { let root = redis_key .get_value()? .ok_or_else(RedisError::nonexistent_key)?; @@ -1627,10 +1547,7 @@ where }) } -fn json_obj_keys_legacy(redis_key: M::ReadHolder, path: &str) -> RedisResult -where - M: Manager, -{ +fn json_obj_keys_legacy(redis_key: M::ReadHolder, path: &str) -> RedisResult { let root = match redis_key.get_value()? { Some(v) => v, _ => return Ok(RedisValue::Null), @@ -1661,35 +1578,27 @@ pub fn json_obj_len(manager: M, ctx: &Context, args: Vec(redis_key: M::ReadHolder, path: &str) -> RedisResult -where - M: Manager, -{ - redis_key.get_value()?.map_or_else( - || { - Err(RedisError::String( - err_msg_json_path_doesnt_exist_with_param_or(path, "not an object"), - )) - }, - |root| { - find_all_values(path, root, |v| v.get_type() == SelectValueType::Object).map(|v| { - v.into_iter() - .map(|v| { - v.map_or(RedisValue::Null, |v| { - RedisValue::Integer(v.len().unwrap() as _) - }) - }) - .collect_vec() - .into() - }) - }, - ) +fn json_obj_len_impl(redis_key: M::ReadHolder, path: &str) -> RedisResult { + redis_key.get_value().and_then(|res| { + res.map_or_else( + || { + Err(RedisError::String( + err_msg_json_path_doesnt_exist_with_param_or(path, "not an object"), + )) + }, + |root| { + find_all_values(path, root, |v| v.get_type() == SelectValueType::Object).map(|v| { + v.into_iter() + .map(|v| v.map_or(RedisValue::Null, |v| v.len().unwrap().into())) + .collect_vec() + .into() + }) + }, + ) + }) } -fn json_obj_len_legacy(redis_key: M::ReadHolder, path: &str) -> RedisResult -where - M: Manager, -{ +fn json_obj_len_legacy(redis_key: M::ReadHolder, path: &str) -> RedisResult { match redis_key.get_value()? { Some(doc) => match KeyValue::new(doc).obj_len(path)? { ObjectLen::Len(l) => Ok(RedisValue::Integer(l as i64)), diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index 3405d634a..80ba32e0d 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -39,7 +39,7 @@ pub struct IValueKeyHolderWrite<'a> { /// The new value is the value returned from `func`, which is called on the current value. /// If the returned value from `func` is [`Err`], the current value remains (although it could be modified by `func`) /// -fn replace(path: &[String], root: &mut IValue, mut func: F) -> bool +fn replace(path: Vec, root: &mut IValue, mut func: F) -> bool where F: FnMut(&mut IValue) -> IValue, { @@ -65,7 +65,7 @@ where /// The value is modified by `func`, which is called on the current value. /// If the returned value from `func` is [`Err`], the current value remains (although it could be modified by `func`) /// -fn update(path: &[String], root: &mut IValue, mut func: F) -> RedisResult +fn update(path: Vec, root: &mut IValue, mut func: F) -> RedisResult where F: FnMut(&mut IValue) -> Result, { @@ -88,7 +88,7 @@ where /// /// Removes a value at a given `path`, starting from `root` /// -fn remove(path: &[String], root: &mut IValue) -> bool { +fn remove(path: Vec, root: &mut IValue) -> bool { path[..path.len() - 1] .iter() .try_fold(root, |target, token| match target.destructure_mut() { @@ -120,7 +120,7 @@ fn remove(path: &[String], root: &mut IValue) -> bool { } impl<'a> IValueKeyHolderWrite<'a> { - fn do_op(&mut self, paths: &[String], op_fun: F) -> RedisResult + fn do_op(&mut self, paths: Vec, op_fun: F) -> RedisResult where F: FnMut(&mut IValue) -> Result, { @@ -141,7 +141,7 @@ impl<'a> IValueKeyHolderWrite<'a> { { let in_value = &serde_json::from_str(num)?; if let serde_json::Value::Number(in_value) = in_value { - self.do_op(&path, |v| { + self.do_op(path, |v| { let num_res = match (v.get_type(), in_value.as_i64()) { (SelectValueType::Long, Some(num2)) => { let num1 = v.get_long(); @@ -221,7 +221,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { // update the root self.set_root(Some(v)).and(Ok(true)) } else { - Ok(replace(&path, self.get_value()?.unwrap(), |_| v.take())) + Ok(replace(path, self.get_value()?.unwrap(), |_| v.take())) } } @@ -232,7 +232,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { merge(root, v); true } else { - replace(&path, root, |current| { + replace(path, root, |current| { merge(current, v.take()); current.take() }) @@ -241,7 +241,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn dict_add(&mut self, path: Vec, key: &str, mut v: IValue) -> RedisResult { - self.do_op(&path, |val| { + self.do_op(path, |val| { val.as_object_mut().map_or(Ok(false), |o| { let res = !o.contains_key(key); if res { @@ -253,7 +253,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn delete_path(&mut self, path: Vec) -> RedisResult { - Ok(remove(&path, self.get_value()?.unwrap())) + Ok(remove(path, self.get_value()?.unwrap())) } fn incr_by(&mut self, path: Vec, num: &str) -> RedisResult { @@ -269,7 +269,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn bool_toggle(&mut self, path: Vec) -> RedisResult { - self.do_op(&path, |v| { + self.do_op(path, |v| { if let DestructuredMut::Bool(mut bool_mut) = v.destructure_mut() { //Using DestructuredMut in order to modify a `Bool` variant let val = bool_mut.get() ^ true; @@ -284,7 +284,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { fn str_append(&mut self, path: Vec, val: String) -> RedisResult { let json = serde_json::from_str(&val)?; if let serde_json::Value::String(s) = json { - self.do_op(&path, |v| { + self.do_op(path, |v| { let v_str = v.as_string_mut().unwrap(); let new_str = [v_str.as_str(), s.as_str()].concat(); *v_str = IString::intern(&new_str); @@ -299,7 +299,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn arr_append(&mut self, path: Vec, args: Vec) -> RedisResult { - self.do_op(&path, |v| { + self.do_op(path, |v| { let arr = v.as_array_mut().unwrap(); for a in &args { arr.push(a.clone()); @@ -314,7 +314,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { args: &[IValue], index: i64, ) -> RedisResult { - self.do_op(&paths, |v| { + self.do_op(paths, |v| { // Verify legal index in bounds let len = v.len().unwrap() as i64; let index = if index < 0 { len + index } else { index }; @@ -336,7 +336,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { where C: FnOnce(Option<&IValue>) -> RedisResult, { - self.do_op(&path, |v| { + self.do_op(path, |v| { v.as_array_mut() .map(|array| { if array.is_empty() { @@ -353,7 +353,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn arr_trim(&mut self, path: Vec, start: i64, stop: i64) -> RedisResult { - self.do_op(&path, |v| { + self.do_op(path, |v| { v.as_array_mut() .map(|array| { let len = array.len() as i64; @@ -378,7 +378,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn clear(&mut self, path: Vec) -> RedisResult { - let cleared = self.do_op(&path, |v| match v.destructure_mut() { + let cleared = self.do_op(path, |v| match v.destructure_mut() { DestructuredMut::Object(obj) => { obj.clear(); Ok(1) diff --git a/redis_json/src/key_value.rs b/redis_json/src/key_value.rs index 777f2c005..f49871297 100644 --- a/redis_json/src/key_value.rs +++ b/redis_json/src/key_value.rs @@ -162,12 +162,12 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { fn to_resp3(&self, paths: Vec, format: ReplyFormatOptions) -> Result { let results = paths .into_iter() - .map(|path: Path| self.to_resp3_path(&path, format)) + .map(|path: Path| self.to_resp3_path(path, format)) .collect(); Ok(RedisValue::Array(results)) } - pub fn to_resp3_path(&self, path: &Path, format: ReplyFormatOptions) -> RedisValue { + pub fn to_resp3_path(&self, path: Path, format: ReplyFormatOptions) -> RedisValue { compile(path.get_path()).map_or(RedisValue::Array(vec![]), |q| { Self::values_to_resp3(calc_once(q, self.val), format) }) From 24951dc638b240bdec0a35aea61a272b3fb7d061 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Tue, 20 Aug 2024 17:55:50 +0300 Subject: [PATCH 20/33] and_then(Ok()) should be map() --- redis_json/src/commands.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index 4240d013b..aae957179 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -271,7 +271,7 @@ pub fn json_merge(manager: M, ctx: &Context, args: Vec) redis_key.dict_add(aui.path, &aui.key, val.clone()) } } - .and_then(|updated| Ok(updated || res)) // If any of the updates succeed, return true + .map(|updated| updated || res) // If any of the updates succeed, return true }) }?; if res { @@ -325,18 +325,20 @@ pub fn json_mset(manager: M, ctx: &Context, args: Vec) Ok(None) } else { // Verify the key is a JSON type - redis_key.get_value()?.map_or_else( - || { - Err(RedisError::Str( - "ERR new objects must be created at the root", - )) - }, - |value| { - KeyValue::new(value) - .find_paths(path.get_path(), SetOptions::None) - .into_both() - }, - ) + redis_key.get_value().and_then(|value| { + value.map_or_else( + || { + Err(RedisError::Str( + "ERR new objects must be created at the root", + )) + }, + |value| { + KeyValue::new(value) + .find_paths(path.get_path(), SetOptions::None) + .into_both() + }, + ) + }) } })?; From 6d50274a835c9e13947f5a93051bced39dd2f3b1 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Wed, 21 Aug 2024 13:25:28 +0300 Subject: [PATCH 21/33] ownership --- json_path/src/json_node.rs | 12 ++---- redis_json/src/commands.rs | 8 ++-- redis_json/src/ivalue_manager.rs | 69 ++++++++++++++++---------------- redis_json/src/manager.rs | 2 +- 4 files changed, 43 insertions(+), 48 deletions(-) diff --git a/json_path/src/json_node.rs b/json_path/src/json_node.rs index 9d33b620c..ca7bffa10 100644 --- a/json_path/src/json_node.rs +++ b/json_path/src/json_node.rs @@ -96,27 +96,21 @@ impl SelectValue for Value { fn get_str(&self) -> String { match self { Self::String(s) => s.to_string(), - _ => { - panic!("not a string"); - } + _ => panic!("not a string"), } } fn as_str(&self) -> &str { match self { Self::String(s) => s.as_str(), - _ => { - panic!("not a string"); - } + _ => panic!("not a string"), } } fn get_bool(&self) -> bool { match self { Self::Bool(b) => *b, - _ => { - panic!("not a bool"); - } + _ => panic!("not a bool"), } } diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index aae957179..b91d5d521 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -469,7 +469,7 @@ where { values .into_iter() - .map(|n| n.map_or_else(|| none_value.clone(), |t| t.into())) + .map(|n| n.map_or_else(|| none_value.clone(), Into::into)) .collect() } @@ -1061,14 +1061,14 @@ fn json_arr_append_legacy( err_msg_json_path_doesnt_exist_with_param_or(path.get_original(), "not an array"), )) } else if paths.len() == 1 { - let res = redis_key.arr_append(paths.pop().unwrap(), args)?; + let res = redis_key.arr_append(paths.pop().unwrap(), &args)?; redis_key.notify_keyspace_event(ctx, "json.arrappend")?; manager.apply_changes(ctx); Ok(res.into()) } else { let mut res = 0; for p in paths { - res = redis_key.arr_append(p, args.clone())?; + res = redis_key.arr_append(p, &args)?; } redis_key.notify_keyspace_event(ctx, "json.arrappend")?; manager.apply_changes(ctx); @@ -1094,7 +1094,7 @@ fn json_arr_append_impl( .map(|p| { p.map_or(Ok(RedisValue::Null), |p| { need_notify = true; - redis_key.arr_append(p, args.clone()).map(Into::into) + redis_key.arr_append(p, &args).map(Into::into) }) }) .try_collect()?; diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index 80ba32e0d..1b2ce5d12 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -282,29 +282,31 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn str_append(&mut self, path: Vec, val: String) -> RedisResult { - let json = serde_json::from_str(&val)?; - if let serde_json::Value::String(s) = json { - self.do_op(path, |v| { - let v_str = v.as_string_mut().unwrap(); - let new_str = [v_str.as_str(), s.as_str()].concat(); - *v_str = IString::intern(&new_str); - Ok(new_str.len()) - }) - } else { - Err(RedisError::String(err_msg_json_expected( + match serde_json::from_str(&val)? { + serde_json::Value::String(s) => self.do_op(path, |v| { + v.as_string_mut() + .map(|v_str| { + let new_str = [v_str.as_str(), s.as_str()].concat(); + *v_str = IString::intern(&new_str); + Ok(new_str.len()) + }) + .unwrap_or_else(|| Err(err_json(v, "string"))) + }), + _ => Err(RedisError::String(err_msg_json_expected( "string", val.as_str(), - ))) + ))), } } - fn arr_append(&mut self, path: Vec, args: Vec) -> RedisResult { + fn arr_append(&mut self, path: Vec, args: &[IValue]) -> RedisResult { self.do_op(path, |v| { - let arr = v.as_array_mut().unwrap(); - for a in &args { - arr.push(a.clone()); - } - Ok(arr.len()) + v.as_array_mut() + .map(|arr| { + args.iter().for_each(|a| arr.push(a.clone())); + Ok(arr.len()) + }) + .unwrap_or_else(|| Err(err_json(v, "array"))) }) } @@ -315,20 +317,20 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { index: i64, ) -> RedisResult { self.do_op(paths, |v| { - // Verify legal index in bounds - let len = v.len().unwrap() as i64; - let index = if index < 0 { len + index } else { index }; - if !(0..=len).contains(&index) { - return Err("ERR index out of bounds".into()); - } - let mut index = index as usize; - let curr = v.as_array_mut().unwrap(); - curr.reserve(args.len()); - for a in args { - curr.insert(index, a.clone()); - index += 1; - } - Ok(curr.len()) + v.as_array_mut() + .map(|arr| { + // Verify legal index in bounds + let len = arr.len() as _; + let index = if index < 0 { len + index } else { index }; + if !(0..=len).contains(&index) { + return Err("ERR index out of bounds".into()); + } + arr.reserve(args.len()); + args.iter().for_each(|a| arr.push(a.clone())); + arr.as_mut_slice()[index as _..].rotate_right(args.len()); + Ok(arr.len()) + }) + .unwrap_or_else(|| Err(err_json(v, "array"))) }) } @@ -378,7 +380,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn clear(&mut self, path: Vec) -> RedisResult { - let cleared = self.do_op(path, |v| match v.destructure_mut() { + self.do_op(path, |v| match v.destructure_mut() { DestructuredMut::Object(obj) => { obj.clear(); Ok(1) @@ -392,8 +394,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { Ok(1) } _ => Ok(0), - })?; - Ok(cleared) + }) } } diff --git a/redis_json/src/manager.rs b/redis_json/src/manager.rs index fb174f8e7..dbb188021 100644 --- a/redis_json/src/manager.rs +++ b/redis_json/src/manager.rs @@ -47,7 +47,7 @@ pub trait WriteHolder { fn pow_by(&mut self, path: Vec, num: &str) -> RedisResult; fn bool_toggle(&mut self, path: Vec) -> RedisResult; fn str_append(&mut self, path: Vec, val: String) -> RedisResult; - fn arr_append(&mut self, path: Vec, args: Vec) -> RedisResult; + fn arr_append(&mut self, path: Vec, args: &[O]) -> RedisResult; fn arr_insert(&mut self, path: Vec, args: &[O], index: i64) -> RedisResult; fn arr_pop(&mut self, path: Vec, index: i64, serialize_callback: C) -> RedisResult where From 33434461c2157aa5c28058f1a176bb6fcb6a62b5 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Wed, 21 Aug 2024 16:40:55 +0300 Subject: [PATCH 22/33] . --- redis_json/src/commands.rs | 10 +-- redis_json/src/ivalue_manager.rs | 3 +- redis_json/src/key_value.rs | 116 ++++++++++++++++--------------- 3 files changed, 66 insertions(+), 63 deletions(-) diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index b91d5d521..6e15f5fec 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -343,9 +343,11 @@ pub fn json_mset(manager: M, ctx: &Context, args: Vec) })?; // Parse the input and validate it's valid JSON - let value = value - .try_as_str() - .and_then(|value| manager.from_str(value, Format::JSON, true).into_both())?; + let value = value.try_as_str().and_then(|value| { + manager + .from_str(value, Format::JSON, true) + .map_err(Into::into) + })?; Ok((redis_key, update_info, value)) }) @@ -771,7 +773,7 @@ fn json_num_op_legacy( NumOp::Mult => redis_key.mult_by(p, number), NumOp::Pow => redis_key.pow_by(p, number), } - .into_both() + .map(Into::into) }) .transpose() .unwrap_or_else(|| { diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index 1b2ce5d12..8cf172ea6 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -325,8 +325,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { if !(0..=len).contains(&index) { return Err("ERR index out of bounds".into()); } - arr.reserve(args.len()); - args.iter().for_each(|a| arr.push(a.clone())); + arr.extend(args.iter().map(IValue::clone)); arr.as_mut_slice()[index as _..].rotate_right(args.len()); Ok(arr.len()) }) diff --git a/redis_json/src/key_value.rs b/redis_json/src/key_value.rs index f49871297..90197147d 100644 --- a/redis_json/src/key_value.rs +++ b/redis_json/src/key_value.rs @@ -18,7 +18,7 @@ use crate::{ err_msg_json_expected, err_msg_json_path_doesnt_exist_with_param, AddUpdateInfo, SetUpdateInfo, UpdateInfo, }, - redisjson::{normalize_arr_indices, Path, ReplyFormat, ResultInto, SetOptions}, + redisjson::{normalize_arr_indices, Path, ReplyFormat, SetOptions}, }; pub struct KeyValue<'a, V: SelectValue> { @@ -31,25 +31,28 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } pub fn get_first<'b>(&'a self, path: &'b str) -> Result<&'a V, Error> { - self.get_values(path)?.first().copied().ok_or( - err_msg_json_path_doesnt_exist_with_param(path) - .as_str() - .into(), - ) + self.get_values(path).and_then(|v| { + v.first().copied().ok_or_else(|| { + err_msg_json_path_doesnt_exist_with_param(path) + .as_str() + .into() + }) + }) } pub fn resp_serialize(&self, path: Path) -> RedisResult { - let res = if path.is_legacy() { - let v = self.get_first(path.get_path())?; - Self::resp_serialize_inner(v) + if path.is_legacy() { + self.get_first(path.get_path()) + .map(Self::resp_serialize_inner) } else { - self.get_values(path.get_path())? - .into_iter() - .map(|v| Self::resp_serialize_inner(v)) - .collect_vec() - .into() - }; - Ok(res) + self.get_values(path.get_path()).map(|v| { + v.into_iter() + .map(Self::resp_serialize_inner) + .collect_vec() + .into() + }) + } + .map_err(Into::into) } fn resp_serialize_inner(v: &V) -> RedisValue { @@ -69,7 +72,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { SelectValueType::Array => { let res = std::iter::once(RedisValue::SimpleStringStatic("[")) - .chain(v.values().unwrap().map(|v| Self::resp_serialize_inner(v))) + .chain(v.values().unwrap().map(Self::resp_serialize_inner)) .collect(); RedisValue::Array(res) } @@ -89,9 +92,9 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } pub fn get_values<'b>(&'a self, path: &'b str) -> Result, Error> { - let query = compile(path)?; - let results = calc_once(query, self.val); - Ok(results) + compile(path) + .map(|query| calc_once(query, self.val)) + .map_err(Into::into) } pub fn serialize_object(o: &O, format: ReplyFormatOptions) -> String { @@ -180,12 +183,12 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { is_legacy: bool, ) -> Result { if is_legacy { - self.to_string_single(path, format).into_both() + self.to_string_single(path, format).map(Into::into) } else if format.is_resp3_reply() { - let values = self.get_values(path)?; - Ok(Self::values_to_resp3(values, format)) + self.get_values(path) + .map(|values| Self::values_to_resp3(values, format)) } else { - self.to_string_multi(path, format).into_both() + self.to_string_multi(path, format).map(Into::into) } } @@ -330,18 +333,17 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { path: &str, format: ReplyFormatOptions, ) -> Result { - let result = self.get_first(path)?; - Ok(Self::serialize_object(&result, format)) + self.get_first(path) + .map(|first| Self::serialize_object(first, format)) } pub fn to_string_multi(&self, path: &str, format: ReplyFormatOptions) -> Result { - let results = self.get_values(path)?; - Ok(Self::serialize_object(&results, format)) + self.get_values(path) + .map(|val| Self::serialize_object(&val, format)) } pub fn get_type(&self, path: &str) -> Result { - let s = Self::value_name(self.get_first(path)?); - Ok(s.to_string()) + self.get_first(path).map(Self::value_name).map(Into::into) } pub fn value_name(value: &V) -> &str { @@ -377,17 +379,17 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } pub fn obj_len(&self, path: &str) -> Result { - match self.get_first(path) { - Ok(first) => match first.get_type() { - SelectValueType::Object => Ok(ObjectLen::Len(first.len().unwrap())), - _ => Err( - err_msg_json_expected("object", self.get_type(path).unwrap().as_str()) - .as_str() - .into(), - ), - }, - _ => Ok(ObjectLen::NoneExisting), - } + self.get_first(path) + .map_or(Ok(ObjectLen::NoneExisting), |first| { + match first.get_type() { + SelectValueType::Object => Ok(ObjectLen::Len(first.len().unwrap())), + _ => Err(err_msg_json_expected( + "object", + self.get_type(path).unwrap().as_str(), + ) + .into()), + } + }) } pub fn is_equal(a: &T1, b: &T2) -> bool { @@ -424,14 +426,13 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { start: i64, end: i64, ) -> Result { - let res = self - .get_values(path)? - .into_iter() - .map(|value| { - RedisValue::from(Self::arr_first_index_single(value, &json_value, start, end)) - }) - .collect_vec(); - Ok(res.into()) + self.get_values(path).map(|v| { + v.into_iter() + .map(|value| Self::arr_first_index_single(value, &json_value, start, end)) + .map(RedisValue::from) + .collect_vec() + .into() + }) } pub fn arr_index_legacy( @@ -441,14 +442,15 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { start: i64, end: i64, ) -> Result { - let arr = self.get_first(path)?; - match Self::arr_first_index_single(arr, &json_value, start, end) { - FoundIndex::NotArray => Err(Error::from(err_msg_json_expected( - "array", - self.get_type(path).unwrap().as_str(), - ))), - i => Ok(i.into()), - } + self.get_first(path).and_then(|arr| { + match Self::arr_first_index_single(arr, &json_value, start, end) { + FoundIndex::NotArray => Err(Error::from(err_msg_json_expected( + "array", + self.get_type(path).unwrap().as_str(), + ))), + i => Ok(i.into()), + } + }) } /// Returns first array index of `v` in `arr`, or `NotFound` if not found in `arr`, or `NotArray` if `arr` is not an array From 4ecbe571c7c46c5babdee1fcee7435f98c5b94ca Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Wed, 21 Aug 2024 17:19:19 +0300 Subject: [PATCH 23/33] dedup --- redis_json/src/commands.rs | 16 ++++---- redis_json/src/ivalue_manager.rs | 70 +++++++++++--------------------- 2 files changed, 33 insertions(+), 53 deletions(-) diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index 6e15f5fec..fb88d400a 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -550,13 +550,15 @@ pub fn json_del(manager: M, ctx: &Context, args: Vec) - redis_key.delete()?; 1 } else { - let paths = - find_paths(path.get_path(), doc, |_| true).map(prepare_paths_for_updating)?; - paths.into_iter().try_fold(0i64, |acc, p| { - redis_key - .delete_path(p) - .map(|deleted| acc + if deleted { 1 } else { 0 }) - })? + find_paths(path.get_path(), doc, |_| true) + .map(prepare_paths_for_updating) + .and_then(|paths| { + paths.into_iter().try_fold(0i64, |acc, p| { + redis_key + .delete_path(p) + .map(|deleted| acc + if deleted { 1 } else { 0 }) + }) + })? }; if res > 0 { redis_key.notify_keyspace_event(ctx, "json.del")?; diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index 8cf172ea6..65db039fe 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -33,16 +33,7 @@ pub struct IValueKeyHolderWrite<'a> { val: Option<&'a mut RedisJSON>, } -/// -/// Replaces a value at a given `path`, starting from `root` -/// -/// The new value is the value returned from `func`, which is called on the current value. -/// If the returned value from `func` is [`Err`], the current value remains (although it could be modified by `func`) -/// -fn replace(path: Vec, root: &mut IValue, mut func: F) -> bool -where - F: FnMut(&mut IValue) -> IValue, -{ +fn follow_path(path: Vec, root: &mut IValue) -> Option<&mut IValue> { path.iter() .try_fold(root, |target, token| match target.destructure_mut() { DestructuredMut::Object(obj) => obj.get_mut(token.as_str()), @@ -55,8 +46,19 @@ where } _ => None, }) - .map(|v| *v = func(v)) - .is_some() +} + +/// +/// Replaces a value at a given `path`, starting from `root` +/// +/// The new value is the value returned from `func`, which is called on the current value. +/// If the returned value from `func` is [`Err`], the current value remains (although it could be modified by `func`) +/// +fn replace(path: Vec, root: &mut IValue, mut func: F) -> bool +where + F: FnMut(&mut IValue) -> IValue, +{ + follow_path(path, root).map(|v| *v = func(v)).is_some() } /// @@ -69,53 +71,29 @@ fn update(path: Vec, root: &mut IValue, mut func: F) -> RedisResul where F: FnMut(&mut IValue) -> Result, { - path.iter() - .try_fold(root, |target, token| match target.destructure_mut() { - DestructuredMut::Object(obj) => obj.get_mut(token.as_str()), - DestructuredMut::Array(arr) => { - let idx = token.parse::().expect(&format!( - "An array index is parsed successfully. Array = {:?}, index = {:?}", - arr, token - )); - arr.get_mut(idx) - } - _ => None, - }) - .map(|v| func(v).map_err(Into::into)) - .unwrap_or_else(|| Err(RedisError::String(err_msg_json_path_doesnt_exist()))) + follow_path(path, root).map_or_else( + || Err(RedisError::String(err_msg_json_path_doesnt_exist())), + |v| func(v).map_err(Into::into), + ) } /// /// Removes a value at a given `path`, starting from `root` /// -fn remove(path: Vec, root: &mut IValue) -> bool { - path[..path.len() - 1] - .iter() - .try_fold(root, |target, token| match target.destructure_mut() { - DestructuredMut::Object(obj) => obj.get_mut(token.as_str()), +fn remove(mut path: Vec, root: &mut IValue) -> bool { + let token = path.pop().unwrap(); + follow_path(path, root) + .and_then(|target| match target.destructure_mut() { + DestructuredMut::Object(obj) => obj.remove(token.as_str()), DestructuredMut::Array(arr) => { let idx = token.parse::().expect(&format!( "An array index is parsed successfully. Array = {:?}, index = {:?}", arr, token )); - arr.get_mut(idx) + arr.remove(idx) } _ => None, }) - .and_then(|target| { - let token = &path[path.len() - 1]; - match target.destructure_mut() { - DestructuredMut::Object(obj) => obj.remove(token.as_str()), - DestructuredMut::Array(arr) => { - let idx = token.parse::().expect(&format!( - "An array index is parsed successfully. Array = {:?}, index = {:?}", - arr, token - )); - arr.remove(idx) - } - _ => None, - } - }) .is_some() } From e63af1e79bd0ea3371c986395af2fc201440bf1b Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Wed, 21 Aug 2024 17:37:13 +0300 Subject: [PATCH 24/33] update and replace are the same thing --- redis_json/src/ivalue_manager.rs | 36 +++++++++----------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index 65db039fe..e5350e230 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -48,33 +48,19 @@ fn follow_path(path: Vec, root: &mut IValue) -> Option<&mut IValue> { }) } -/// -/// Replaces a value at a given `path`, starting from `root` -/// -/// The new value is the value returned from `func`, which is called on the current value. -/// If the returned value from `func` is [`Err`], the current value remains (although it could be modified by `func`) -/// -fn replace(path: Vec, root: &mut IValue, mut func: F) -> bool -where - F: FnMut(&mut IValue) -> IValue, -{ - follow_path(path, root).map(|v| *v = func(v)).is_some() -} - /// /// Updates a value at a given `path`, starting from `root` /// /// The value is modified by `func`, which is called on the current value. /// If the returned value from `func` is [`Err`], the current value remains (although it could be modified by `func`) /// -fn update(path: Vec, root: &mut IValue, mut func: F) -> RedisResult +fn update(path: Vec, root: &mut IValue, func: F) -> RedisResult where F: FnMut(&mut IValue) -> Result, { - follow_path(path, root).map_or_else( - || Err(RedisError::String(err_msg_json_path_doesnt_exist())), - |v| func(v).map_err(Into::into), - ) + follow_path(path, root) + .map_or_else(|| Err(err_msg_json_path_doesnt_exist().into()), func) + .map_err(Into::into) } /// @@ -102,8 +88,8 @@ impl<'a> IValueKeyHolderWrite<'a> { where F: FnMut(&mut IValue) -> Result, { - let root = self.get_value()?.unwrap(); - update(paths, root, op_fun) + self.get_value() + .and_then(|root| update(paths, root.unwrap(), op_fun)) } fn do_num_op( @@ -199,7 +185,8 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { // update the root self.set_root(Some(v)).and(Ok(true)) } else { - Ok(replace(path, self.get_value()?.unwrap(), |_| v.take())) + self.get_value() + .map(|root| update(path, root.unwrap(), |val| Ok(*val = v.take())).is_ok()) } } @@ -210,10 +197,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { merge(root, v); true } else { - replace(path, root, |current| { - merge(current, v.take()); - current.take() - }) + update(path, root, |current| Ok(merge(current, v.take()))).is_ok() }; Ok(updated) } @@ -231,7 +215,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { } fn delete_path(&mut self, path: Vec) -> RedisResult { - Ok(remove(path, self.get_value()?.unwrap())) + self.get_value().map(|root| remove(path, root.unwrap())) } fn incr_by(&mut self, path: Vec, num: &str) -> RedisResult { From 27822b52853dd9fd669cee484b2870140d473efe Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Thu, 22 Aug 2024 14:08:11 +0300 Subject: [PATCH 25/33] removing dead code --- redis_json/src/commands.rs | 4 +- redis_json/src/ivalue_manager.rs | 92 +++++++++++++------------------- 2 files changed, 40 insertions(+), 56 deletions(-) diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index fb88d400a..27884bf1b 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -395,10 +395,10 @@ where } } -fn find_paths(path: &str, doc: &T, mut f: F) -> RedisResult>> +fn find_paths(path: &str, doc: &T, f: F) -> RedisResult>> where T: SelectValue, - F: FnMut(&T) -> bool, + F: Fn(&T) -> bool, { let query = compile(path).map_err(|e| RedisError::String(e.to_string()))?; let res = calc_once_with_paths(query, doc) diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index e5350e230..9cc55d077 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -56,7 +56,7 @@ fn follow_path(path: Vec, root: &mut IValue) -> Option<&mut IValue> { /// fn update(path: Vec, root: &mut IValue, func: F) -> RedisResult where - F: FnMut(&mut IValue) -> Result, + F: FnOnce(&mut IValue) -> Result, { follow_path(path, root) .map_or_else(|| Err(err_msg_json_path_doesnt_exist().into()), func) @@ -86,54 +86,51 @@ fn remove(mut path: Vec, root: &mut IValue) -> bool { impl<'a> IValueKeyHolderWrite<'a> { fn do_op(&mut self, paths: Vec, op_fun: F) -> RedisResult where - F: FnMut(&mut IValue) -> Result, + F: FnOnce(&mut IValue) -> Result, { - self.get_value() - .and_then(|root| update(paths, root.unwrap(), op_fun)) + let root = self.get_value()?.unwrap(); + update(paths, root, op_fun) } fn do_num_op( &mut self, path: Vec, num: &str, - mut op1_fun: F1, - mut op2_fun: F2, + op1: F1, + op2: F2, ) -> RedisResult where - F1: FnMut(i64, i64) -> i64, - F2: FnMut(f64, f64) -> f64, + F1: FnOnce(i64, i64) -> i64, + F2: FnOnce(f64, f64) -> f64, { let in_value = &serde_json::from_str(num)?; if let serde_json::Value::Number(in_value) = in_value { - self.do_op(path, |v| { - let num_res = match (v.get_type(), in_value.as_i64()) { + let n = self.do_op(path, |v| { + let new_val = match (v.get_type(), in_value.as_i64()) { (SelectValueType::Long, Some(num2)) => { let num1 = v.get_long(); - let res = op1_fun(num1, num2); - Ok(res.into()) + Ok(op1(num1, num2).into()) } _ => { let num1 = v.get_double(); let num2 = in_value.as_f64().unwrap(); - INumber::try_from(op2_fun(num1, num2)) + INumber::try_from(op2(num1, num2)) .map_err(|_| RedisError::Str("result is not a number")) } - }; - let new_val = IValue::from(num_res?); + } + .map(IValue::from)?; *v = new_val.clone(); Ok(new_val) - }) - .and_then(|n| { - n.as_number() - .and_then(|n| { - if n.has_decimal_point() { - n.to_f64().and_then(serde_json::Number::from_f64) - } else { - n.to_i64().map(Into::into) - } - }) - .ok_or_else(|| RedisError::Str("result is not a number")) - }) + })?; + n.as_number() + .and_then(|n| { + if n.has_decimal_point() { + n.to_f64().and_then(serde_json::Number::from_f64) + } else { + n.to_i64().map(Into::into) + } + }) + .ok_or_else(|| RedisError::Str("result is not a number")) } else { Err(RedisError::Str("bad input number")) } @@ -146,17 +143,12 @@ impl<'a> IValueKeyHolderWrite<'a> { Ok(()) } - fn set_root(&mut self, v: Option) -> RedisResult<()> { - if let Some(data) = v { - self.get_json_holder()?; - if let Some(val) = &mut self.val { - val.data = data - } else { - self.key.set_value(&REDIS_JSON_TYPE, RedisJSON { data })? - } + fn set_root(&mut self, data: IValue) -> RedisResult<()> { + self.get_json_holder()?; + if let Some(val) = &mut self.val { + val.data = data } else { - self.val = None; - self.key.delete()?; + self.key.set_value(&REDIS_JSON_TYPE, RedisJSON { data })? } Ok(()) } @@ -176,30 +168,22 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { fn get_value(&mut self) -> RedisResult> { self.get_json_holder()?; - let val = self.val.as_mut().map(|v| &mut v.data); - Ok(val) + Ok(self.val.as_mut().map(|v| &mut v.data)) } fn set_value(&mut self, path: Vec, mut v: IValue) -> RedisResult { if path.is_empty() { // update the root - self.set_root(Some(v)).and(Ok(true)) + self.set_root(v).and(Ok(true)) } else { - self.get_value() - .map(|root| update(path, root.unwrap(), |val| Ok(*val = v.take())).is_ok()) + let root = self.get_value()?.unwrap(); + Ok(update(path, root, |val| Ok(*val = v.take())).is_ok()) } } fn merge_value(&mut self, path: Vec, mut v: IValue) -> RedisResult { let root = self.get_value()?.unwrap(); - let updated = if path.is_empty() { - // update the root - merge(root, v); - true - } else { - update(path, root, |current| Ok(merge(current, v.take()))).is_ok() - }; - Ok(updated) + Ok(update(path, root, |current| Ok(merge(current, v.take()))).is_ok()) } fn dict_add(&mut self, path: Vec, key: &str, mut v: IValue) -> RedisResult { @@ -288,7 +272,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { return Err("ERR index out of bounds".into()); } arr.extend(args.iter().map(IValue::clone)); - arr.as_mut_slice()[index as _..].rotate_right(args.len()); + arr[index as _..].rotate_right(args.len()); Ok(arr.len()) }) .unwrap_or_else(|| Err(err_json(v, "array"))) @@ -299,7 +283,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { where C: FnOnce(Option<&IValue>) -> RedisResult, { - self.do_op(path, |v| { + let res = self.do_op(path, |v| { v.as_array_mut() .map(|array| { if array.is_empty() { @@ -311,8 +295,8 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { array.remove(index) }) .ok_or_else(|| err_json(v, "array")) - }) - .and_then(|res| serialize_callback(res.as_ref())) + })?; + serialize_callback(res.as_ref()) } fn arr_trim(&mut self, path: Vec, start: i64, stop: i64) -> RedisResult { From eb70f7128230dc7b36d48767778d5d90f5dcc6b8 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Thu, 22 Aug 2024 16:51:35 +0300 Subject: [PATCH 26/33] readability --- redis_json/src/commands.rs | 2 +- redis_json/src/ivalue_manager.rs | 2 +- redis_json/src/key_value.rs | 104 ++++++++++++++----------------- 3 files changed, 48 insertions(+), 60 deletions(-) diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index 27884bf1b..4fa58dbec 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -704,7 +704,7 @@ fn json_num_op( // Convert to RESP2 format return as one JSON array let values = to_json_value::(results, Value::Null); - Ok(KeyValue::::serialize_object(&values, ReplyFormatOptions::default()).into()) + Ok(KeyValue::::serialize_object(values, ReplyFormatOptions::default()).into()) } } diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index 9cc55d077..e007a9932 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -271,7 +271,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { if !(0..=len).contains(&index) { return Err("ERR index out of bounds".into()); } - arr.extend(args.iter().map(IValue::clone)); + arr.extend(args.iter().cloned()); arr[index as _..].rotate_right(args.len()); Ok(arr.len()) }) diff --git a/redis_json/src/key_value.rs b/redis_json/src/key_value.rs index 90197147d..4e3541d21 100644 --- a/redis_json/src/key_value.rs +++ b/redis_json/src/key_value.rs @@ -31,28 +31,25 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } pub fn get_first<'b>(&'a self, path: &'b str) -> Result<&'a V, Error> { - self.get_values(path).and_then(|v| { - v.first().copied().ok_or_else(|| { - err_msg_json_path_doesnt_exist_with_param(path) - .as_str() - .into() - }) + let results = self.get_values(path)?; + results.first().copied().ok_or_else(|| { + err_msg_json_path_doesnt_exist_with_param(path) + .as_str() + .into() }) } pub fn resp_serialize(&self, path: Path) -> RedisResult { if path.is_legacy() { - self.get_first(path.get_path()) - .map(Self::resp_serialize_inner) + let v = self.get_first(path.get_path())?; + Ok(Self::resp_serialize_inner(v)) } else { - self.get_values(path.get_path()).map(|v| { - v.into_iter() - .map(Self::resp_serialize_inner) - .collect_vec() - .into() - }) + let v = self.get_values(path.get_path())?; + Ok(v.into_iter() + .map(Self::resp_serialize_inner) + .collect_vec() + .into()) } - .map_err(Into::into) } fn resp_serialize_inner(v: &V) -> RedisValue { @@ -92,15 +89,14 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } pub fn get_values<'b>(&'a self, path: &'b str) -> Result, Error> { - compile(path) - .map(|query| calc_once(query, self.val)) - .map_err(Into::into) + let query = compile(path)?; + Ok(calc_once(query, self.val)) } - pub fn serialize_object(o: &O, format: ReplyFormatOptions) -> String { + pub fn serialize_object(o: O, format: ReplyFormatOptions) -> String { // When using the default formatting, we can use serde_json's default serializer if format.no_formatting() { - serde_json::to_string(o).unwrap() + serde_json::to_string(&o).unwrap() } else { let formatter = RedisJsonFormatter::new(format); let mut out = serde_json::Serializer::with_formatter(Vec::new(), formatter); @@ -157,7 +153,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { .collect(); RedisValue::Map(map) } else { - Self::serialize_object(&temp_doc, format).into() + Self::serialize_object(temp_doc, format).into() }; Ok(res) } @@ -185,8 +181,8 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { if is_legacy { self.to_string_single(path, format).map(Into::into) } else if format.is_resp3_reply() { - self.get_values(path) - .map(|values| Self::values_to_resp3(values, format)) + let values = self.get_values(path)?; + Ok(Self::values_to_resp3(values, format)) } else { self.to_string_multi(path, format).map(Into::into) } @@ -270,15 +266,15 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { match token_type { JsonPathToken::String => { - let res = if query.size() == 1 { + if query.size() == 1 { // Adding to the root - vec![UpdateInfo::AUI(AddUpdateInfo { + Ok(vec![UpdateInfo::AUI(AddUpdateInfo { path: Vec::new(), key: last, - })] + })]) } else { // Adding somewhere in existing object - calc_once_paths(query, self.val) + Ok(calc_once_paths(query, self.val) .into_iter() .map(|v| { UpdateInfo::AUI(AddUpdateInfo { @@ -286,9 +282,8 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { key: last.to_string(), }) }) - .collect() - }; - Ok(res) + .collect()) + } } JsonPathToken::Number => { // if we reach here with array path we are either out of range @@ -328,18 +323,13 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } } - pub fn to_string_single( - &self, - path: &str, - format: ReplyFormatOptions, - ) -> Result { - self.get_first(path) - .map(|first| Self::serialize_object(first, format)) + pub fn to_string_single(&self, path: &str, fmt: ReplyFormatOptions) -> Result { + self.get_first(path).map(|o| Self::serialize_object(o, fmt)) } - pub fn to_string_multi(&self, path: &str, format: ReplyFormatOptions) -> Result { + pub fn to_string_multi(&self, path: &str, fmt: ReplyFormatOptions) -> Result { self.get_values(path) - .map(|val| Self::serialize_object(&val, format)) + .map(|o| Self::serialize_object(o, fmt)) } pub fn get_type(&self, path: &str) -> Result { @@ -383,11 +373,10 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { .map_or(Ok(ObjectLen::NoneExisting), |first| { match first.get_type() { SelectValueType::Object => Ok(ObjectLen::Len(first.len().unwrap())), - _ => Err(err_msg_json_expected( + _ => Err(Error::from(err_msg_json_expected( "object", self.get_type(path).unwrap().as_str(), - ) - .into()), + ))), } }) } @@ -426,13 +415,13 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { start: i64, end: i64, ) -> Result { - self.get_values(path).map(|v| { - v.into_iter() - .map(|value| Self::arr_first_index_single(value, &json_value, start, end)) - .map(RedisValue::from) - .collect_vec() - .into() - }) + let values = self.get_values(path)?; + Ok(values + .into_iter() + .map(|value| Self::arr_first_index_single(value, &json_value, start, end)) + .map(RedisValue::from) + .collect_vec() + .into()) } pub fn arr_index_legacy( @@ -442,15 +431,14 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { start: i64, end: i64, ) -> Result { - self.get_first(path).and_then(|arr| { - match Self::arr_first_index_single(arr, &json_value, start, end) { - FoundIndex::NotArray => Err(Error::from(err_msg_json_expected( - "array", - self.get_type(path).unwrap().as_str(), - ))), - i => Ok(i.into()), - } - }) + let arr = self.get_first(path)?; + match Self::arr_first_index_single(arr, &json_value, start, end) { + FoundIndex::NotArray => Err(Error::from(err_msg_json_expected( + "array", + self.get_type(path).unwrap().as_str(), + ))), + i => Ok(i.into()), + } } /// Returns first array index of `v` in `arr`, or `NotFound` if not found in `arr`, or `NotArray` if `arr` is not an array From bf5cb8bef6d1e64dd8b6359d3e80f070fae2f0fa Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Fri, 23 Aug 2024 00:11:36 +0300 Subject: [PATCH 27/33] strong typing is good, why use enums where they're not needed --- redis_json/src/commands.rs | 43 +++++++++++--------------------- redis_json/src/ivalue_manager.rs | 8 ++---- 2 files changed, 17 insertions(+), 34 deletions(-) diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index 4fa58dbec..642525f3a 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -317,39 +317,26 @@ pub fn json_mset(manager: M, ctx: &Context, args: Vec) let [key, path, value] = args else { unreachable!(); }; - let mut redis_key = manager.open_key_write(ctx, key.safe_clone(ctx))?; + let mut key = manager.open_key_write(ctx, key.safe_clone(ctx))?; // Verify the path is valid and get all the update info - let update_info = path.try_as_str().map(Path::new).and_then(|path| { - if path == *JSON_ROOT_PATH { - Ok(None) - } else { - // Verify the key is a JSON type - redis_key.get_value().and_then(|value| { - value.map_or_else( - || { - Err(RedisError::Str( - "ERR new objects must be created at the root", - )) - }, - |value| { - KeyValue::new(value) - .find_paths(path.get_path(), SetOptions::None) - .into_both() - }, - ) - }) - } - })?; + let path = path.try_as_str().map(Path::new)?; + let update_info = if path == *JSON_ROOT_PATH { + Ok(None) + } else if let Some(value) = key.get_value()? { + KeyValue::new(value) + .find_paths(path.get_path(), SetOptions::None) + .into_both() + } else { + Err(RedisError::Str( + "ERR new objects must be created at the root", + )) + }?; // Parse the input and validate it's valid JSON - let value = value.try_as_str().and_then(|value| { - manager - .from_str(value, Format::JSON, true) - .map_err(Into::into) - })?; + let value = manager.from_str(value.try_as_str()?, Format::JSON, true)?; - Ok((redis_key, update_info, value)) + Ok((key, update_info, value)) }) .try_collect()?; diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index e007a9932..bf9df2c22 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -117,19 +117,15 @@ impl<'a> IValueKeyHolderWrite<'a> { INumber::try_from(op2(num1, num2)) .map_err(|_| RedisError::Str("result is not a number")) } - } - .map(IValue::from)?; - *v = new_val.clone(); + }?; + *v = IValue::from(new_val.clone()); Ok(new_val) })?; - n.as_number() - .and_then(|n| { if n.has_decimal_point() { n.to_f64().and_then(serde_json::Number::from_f64) } else { n.to_i64().map(Into::into) } - }) .ok_or_else(|| RedisError::Str("result is not a number")) } else { Err(RedisError::Str("bad input number")) From 3018e35a093a1792ec72d291a5ee5679a1f6b939 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Sun, 25 Aug 2024 14:28:03 +0300 Subject: [PATCH 28/33] reduce number of wheels needing reinvention --- Cargo.lock | 1 + json_path/Cargo.toml | 1 + json_path/src/json_path.rs | 214 ++++++++++++------------------- json_path/src/lib.rs | 8 +- redis_json/src/commands.rs | 60 +++------ redis_json/src/ivalue_manager.rs | 80 +++++------- redis_json/src/key_value.rs | 83 ++++-------- redis_json/src/manager.rs | 35 +++-- 8 files changed, 188 insertions(+), 294 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 061445433..652cda5fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -487,6 +487,7 @@ dependencies = [ "log", "pest", "pest_derive", + "redis-module", "regex", "serde", "serde_json", diff --git a/json_path/Cargo.toml b/json_path/Cargo.toml index dd7bbdc53..53290afd7 100644 --- a/json_path/Cargo.toml +++ b/json_path/Cargo.toml @@ -15,6 +15,7 @@ ijson.workspace = true log = "0.4" regex = "1" itertools = "0.13" +redis-module ={ version = "^2.0.7", default-features = false, features = ["min-redis-compatibility-version-7-2"] } [dev-dependencies] env_logger = "0.11" diff --git a/json_path/src/json_path.rs b/json_path/src/json_path.rs index 4ac0715ba..8304b90b0 100644 --- a/json_path/src/json_path.rs +++ b/json_path/src/json_path.rs @@ -8,6 +8,7 @@ use itertools::Itertools; use pest::iterators::{Pair, Pairs}; use pest::Parser; use pest_derive::Parser; +use redis_module::RedisError; use std::cmp::Ordering; use crate::select_value::{SelectValue, SelectValueType}; @@ -115,6 +116,12 @@ impl std::fmt::Display for QueryCompilationError { } } +impl From for RedisError { + fn from(e: QueryCompilationError) -> Self { + Self::String(e.to_string()) + } +} + impl std::fmt::Display for Rule { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { match self { @@ -134,49 +141,39 @@ impl std::fmt::Display for Rule { /// Compile the given string query into a query object. /// Returns error on compilation error. pub(crate) fn compile(path: &str) -> Result { - let query = JsonPathParser::parse(Rule::query, path); - match query { - Ok(mut q) => { + JsonPathParser::parse(Rule::query, path) + .map(|mut q| { let root = q.next().unwrap(); - Ok(Query { + Query { root: root.into_inner(), is_static: None, size: None, - }) - } - // pest::error::Error - Err(e) => { + } + }) + .map_err(|e| { let pos = match e.location { pest::error::InputLocation::Pos(pos) => pos, pest::error::InputLocation::Span((pos, _end)) => pos, }; let msg = match e.variant { pest::error::ErrorVariant::ParsingError { - ref positives, - ref negatives, + positives, + negatives, } => { - let positives = if positives.is_empty() { - None - } else { - Some( - positives - .iter() - .map(|v| format!("{v}")) - .collect_vec() - .join(", "), - ) - }; - let negatives = if negatives.is_empty() { - None - } else { - Some( - negatives - .iter() - .map(|v| format!("{v}")) - .collect_vec() - .join(", "), - ) - }; + let positives = (!positives.is_empty()).then(|| { + positives + .into_iter() + .map(|v| format!("{v}")) + .collect_vec() + .join(", ") + }); + let negatives = (!negatives.is_empty()).then(|| { + negatives + .into_iter() + .map(|v| format!("{v}")) + .collect_vec() + .join(", ") + }); match (positives, negatives) { (None, None) => "parsing error".to_string(), @@ -187,7 +184,7 @@ pub(crate) fn compile(path: &str) -> Result { ), } } - pest::error::ErrorVariant::CustomError { ref message } => message.clone(), + pest::error::ErrorVariant::CustomError { message } => message, }; let final_msg = if pos == path.len() { @@ -195,12 +192,11 @@ pub(crate) fn compile(path: &str) -> Result { } else { format!("\"{} ---->>>> {}\", {}.", &path[..pos], &path[pos..], msg) }; - Err(QueryCompilationError { + QueryCompilationError { location: pos, message: final_msg, - }) - } - } + } + }) } pub trait UserPathTracker { @@ -334,116 +330,80 @@ enum TermEvaluationResult<'i, 'j, S: SelectValue> { Invalid, } -enum CmpResult { - Ord(Ordering), - NotComparable, +impl<'i, 'j, S: SelectValue> PartialEq for TermEvaluationResult<'i, 'j, S> { + fn eq(&self, s: &Self) -> bool { + match (self, s) { + (TermEvaluationResult::Value(v1), TermEvaluationResult::Value(v2)) => v1 == v2, + (_, _) => match self.partial_cmp(s) { + Some(o) => o.is_eq(), + None => false, + }, + } + } } -impl<'i, 'j, S: SelectValue> TermEvaluationResult<'i, 'j, S> { - fn cmp(&self, s: &Self) -> CmpResult { +impl<'i, 'j, S: SelectValue> PartialOrd for TermEvaluationResult<'i, 'j, S> { + fn partial_cmp(&self, s: &Self) -> Option { match (self, s) { (TermEvaluationResult::Integer(n1), TermEvaluationResult::Integer(n2)) => { - CmpResult::Ord(n1.cmp(n2)) + Some(n1.cmp(n2)) } (TermEvaluationResult::Float(_), TermEvaluationResult::Integer(n2)) => { - self.cmp(&TermEvaluationResult::Float(*n2 as f64)) + self.partial_cmp(&TermEvaluationResult::Float(*n2 as f64)) } (TermEvaluationResult::Integer(n1), TermEvaluationResult::Float(_)) => { - TermEvaluationResult::Float(*n1 as f64).cmp(s) + TermEvaluationResult::Float(*n1 as f64).partial_cmp(s) } (TermEvaluationResult::Float(f1), TermEvaluationResult::Float(f2)) => { - if *f1 > *f2 { - CmpResult::Ord(Ordering::Greater) - } else if *f1 < *f2 { - CmpResult::Ord(Ordering::Less) - } else { - CmpResult::Ord(Ordering::Equal) - } - } - (TermEvaluationResult::Str(s1), TermEvaluationResult::Str(s2)) => { - CmpResult::Ord(s1.cmp(s2)) + f1.partial_cmp(f2) } + (TermEvaluationResult::Str(s1), TermEvaluationResult::Str(s2)) => Some(s1.cmp(s2)), (TermEvaluationResult::Str(s1), TermEvaluationResult::String(s2)) => { - CmpResult::Ord((*s1).cmp(s2)) + Some((*s1).cmp(s2)) } (TermEvaluationResult::String(s1), TermEvaluationResult::Str(s2)) => { - CmpResult::Ord((s1[..]).cmp(s2)) + Some((s1[..]).cmp(s2)) } (TermEvaluationResult::String(s1), TermEvaluationResult::String(s2)) => { - CmpResult::Ord(s1.cmp(s2)) - } - (TermEvaluationResult::Bool(b1), TermEvaluationResult::Bool(b2)) => { - CmpResult::Ord(b1.cmp(b2)) - } - (TermEvaluationResult::Null, TermEvaluationResult::Null) => { - CmpResult::Ord(Ordering::Equal) + Some(s1.cmp(s2)) } + (TermEvaluationResult::Bool(b1), TermEvaluationResult::Bool(b2)) => Some(b1.cmp(b2)), + (TermEvaluationResult::Null, TermEvaluationResult::Null) => Some(Ordering::Equal), (TermEvaluationResult::Value(v), _) => match v.get_type() { - SelectValueType::Long => TermEvaluationResult::Integer(v.get_long()).cmp(s), - SelectValueType::Double => TermEvaluationResult::Float(v.get_double()).cmp(s), - SelectValueType::String => TermEvaluationResult::Str(v.as_str()).cmp(s), - SelectValueType::Bool => TermEvaluationResult::Bool(v.get_bool()).cmp(s), - SelectValueType::Null => TermEvaluationResult::Null.cmp(s), - _ => CmpResult::NotComparable, + SelectValueType::Long => TermEvaluationResult::Integer(v.get_long()).partial_cmp(s), + SelectValueType::Double => { + TermEvaluationResult::Float(v.get_double()).partial_cmp(s) + } + SelectValueType::String => TermEvaluationResult::Str(v.as_str()).partial_cmp(s), + SelectValueType::Bool => TermEvaluationResult::Bool(v.get_bool()).partial_cmp(s), + SelectValueType::Null => TermEvaluationResult::Null.partial_cmp(s), + _ => None, }, (_, TermEvaluationResult::Value(v)) => match v.get_type() { - SelectValueType::Long => self.cmp(&TermEvaluationResult::Integer(v.get_long())), - SelectValueType::Double => self.cmp(&TermEvaluationResult::Float(v.get_double())), - SelectValueType::String => self.cmp(&TermEvaluationResult::Str(v.as_str())), - SelectValueType::Bool => self.cmp(&TermEvaluationResult::Bool(v.get_bool())), - SelectValueType::Null => self.cmp(&TermEvaluationResult::Null), - _ => CmpResult::NotComparable, - }, - (_, _) => CmpResult::NotComparable, - } - } - fn gt(&self, s: &Self) -> bool { - match self.cmp(s) { - CmpResult::Ord(o) => o.is_gt(), - CmpResult::NotComparable => false, - } - } - - fn ge(&self, s: &Self) -> bool { - match self.cmp(s) { - CmpResult::Ord(o) => o.is_ge(), - CmpResult::NotComparable => false, - } - } - - fn lt(&self, s: &Self) -> bool { - match self.cmp(s) { - CmpResult::Ord(o) => o.is_lt(), - CmpResult::NotComparable => false, - } - } - - fn le(&self, s: &Self) -> bool { - match self.cmp(s) { - CmpResult::Ord(o) => o.is_le(), - CmpResult::NotComparable => false, - } - } - - fn eq(&self, s: &Self) -> bool { - match (self, s) { - (TermEvaluationResult::Value(v1), TermEvaluationResult::Value(v2)) => v1 == v2, - (_, _) => match self.cmp(s) { - CmpResult::Ord(o) => o.is_eq(), - CmpResult::NotComparable => false, + SelectValueType::Long => { + self.partial_cmp(&TermEvaluationResult::Integer(v.get_long())) + } + SelectValueType::Double => { + self.partial_cmp(&TermEvaluationResult::Float(v.get_double())) + } + SelectValueType::String => self.partial_cmp(&TermEvaluationResult::Str(v.as_str())), + SelectValueType::Bool => { + self.partial_cmp(&TermEvaluationResult::Bool(v.get_bool())) + } + SelectValueType::Null => self.partial_cmp(&TermEvaluationResult::Null), + _ => None, }, + (_, _) => None, } } +} - fn ne(&self, s: &Self) -> bool { - !self.eq(s) - } - +impl<'i, 'j, S: SelectValue> TermEvaluationResult<'i, 'j, S> { fn re_is_match(regex: &str, s: &str) -> bool { Regex::new(regex).map_or(false, |re| Regex::is_match(&re, s)) } - fn re_match(&self, s: &Self) -> bool { + fn re(&self, s: &Self) -> bool { match (self, s) { (TermEvaluationResult::Value(v), TermEvaluationResult::Str(regex)) => { match v.get_type() { @@ -462,10 +422,6 @@ impl<'i, 'j, S: SelectValue> TermEvaluationResult<'i, 'j, S> { (_, _) => false, } } - - fn re(&self, s: &Self) -> bool { - self.re_match(s) - } } /* This struct is used to calculate a json path on a json object. @@ -865,12 +821,12 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { let term2_val = self.evaluate_single_term(term2, json, calc_data); trace!("evaluate_single_filter term2_val {:?}", &term2_val); match op.as_rule() { - Rule::gt => term1_val.gt(&term2_val), - Rule::ge => term1_val.ge(&term2_val), - Rule::lt => term1_val.lt(&term2_val), - Rule::le => term1_val.le(&term2_val), - Rule::eq => term1_val.eq(&term2_val), - Rule::ne => term1_val.ne(&term2_val), + Rule::gt => term1_val > term2_val, + Rule::ge => term1_val >= term2_val, + Rule::lt => term1_val < term2_val, + Rule::le => term1_val <= term2_val, + Rule::eq => term1_val == term2_val, + Rule::ne => term1_val != term2_val, Rule::re => term1_val.re(&term2_val), _ => panic!("{op:?}"), } diff --git a/json_path/src/lib.rs b/json_path/src/lib.rs index 5f84c9d68..e842cfce8 100644 --- a/json_path/src/lib.rs +++ b/json_path/src/lib.rs @@ -8,9 +8,11 @@ pub mod json_node; pub mod json_path; pub mod select_value; +use redis_module::RedisResult; + use crate::json_path::{ CalculationResult, DummyTrackerGenerator, PTracker, PTrackerGenerator, PathCalculator, Query, - QueryCompilationError, UserPathTracker, + UserPathTracker, }; use crate::select_value::SelectValue; @@ -62,8 +64,8 @@ pub const fn create_with_generator<'i>( /// Compile the given json path, compilation results can after be used /// to create `PathCalculator` calculator object to calculate json paths -pub fn compile(s: &str) -> Result { - json_path::compile(s) +pub fn compile(s: &str) -> RedisResult { + json_path::compile(s).map_err(Into::into) } /// Calc once allows to perform a one time calculation on the give query. diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index 642525f3a..e0e24ff31 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -4,12 +4,11 @@ * the Server Side Public License v1 (SSPLv1). */ -use crate::error::Error; use crate::formatter::ReplyFormatOptions; use crate::key_value::KeyValue; use crate::manager::{ - err_msg_json_path_doesnt_exist_with_param, err_msg_json_path_doesnt_exist_with_param_or, - Manager, ReadHolder, UpdateInfo, WriteHolder, + path_doesnt_exist_with_param, path_doesnt_exist_with_param_or, Manager, ReadHolder, UpdateInfo, + WriteHolder, }; use crate::redisjson::{Format, Path, ReplyFormat, ResultInto, SetOptions, JSON_ROOT_PATH}; use json_path::select_value::{SelectValue, SelectValueType}; @@ -387,7 +386,7 @@ where T: SelectValue, F: Fn(&T) -> bool, { - let query = compile(path).map_err(|e| RedisError::String(e.to_string()))?; + let query = compile(path)?; let res = calc_once_with_paths(query, doc) .into_iter() .filter_map(|e| { @@ -404,7 +403,7 @@ fn get_all_values_and_paths<'a, T>(path: &str, doc: &'a T) -> RedisResult( }) .transpose() .unwrap_or_else(|| { - Err(RedisError::String( - err_msg_json_path_doesnt_exist_with_param_or(path, "does not contains a number"), + Err(path_doesnt_exist_with_param_or( + path, + "does not contains a number", )) })?; redis_key.notify_keyspace_event(ctx, cmd)?; @@ -870,9 +870,7 @@ fn json_bool_toggle_legacy( manager.apply_changes(ctx); Ok(res.to_string().into()) } else { - Err(RedisError::String( - err_msg_json_path_doesnt_exist_with_param_or(path, "not a bool"), - )) + Err(path_doesnt_exist_with_param_or(path, "not a bool")) } } @@ -960,9 +958,7 @@ fn json_str_append_legacy( manager.apply_changes(ctx); Ok(res.unwrap().into()) } else { - Err(RedisError::String( - err_msg_json_path_doesnt_exist_with_param_or(path, "not a string"), - )) + Err(path_doesnt_exist_with_param_or(path, "not a string")) } } @@ -1048,8 +1044,9 @@ fn json_arr_append_legacy( v.get_type() == SelectValueType::Array })?; if paths.is_empty() { - Err(RedisError::String( - err_msg_json_path_doesnt_exist_with_param_or(path.get_original(), "not an array"), + Err(path_doesnt_exist_with_param_or( + path.get_original(), + "not an array", )) } else if paths.len() == 1 { let res = redis_key.arr_append(paths.pop().unwrap(), &args)?; @@ -1140,11 +1137,7 @@ where let json_value: Value = serde_json::from_str(value)?; let res = key.get_value()?.map_or_else( - || { - Err(Error::from(err_msg_json_path_doesnt_exist_with_param( - path.get_original(), - ))) - }, + || Err(path_doesnt_exist_with_param(path.get_original())), |doc| { if path.is_legacy() { KeyValue::new(doc).arr_index_legacy(path.get_path(), json_value, start, end) @@ -1234,9 +1227,7 @@ fn json_arr_insert_legacy( let paths = find_paths(path, root, |v| v.get_type() == SelectValueType::Array)?; if paths.is_empty() { - Err(RedisError::String( - err_msg_json_path_doesnt_exist_with_param_or(path, "not an array"), - )) + Err(path_doesnt_exist_with_param_or(path, "not an array")) } else { let mut res = None; for p in paths { @@ -1286,8 +1277,9 @@ fn json_arr_len_legacy(key: M::ReadHolder, path: Path) -> RedisResul })?; values.into_iter().next().flatten().map_or_else( || { - Err(RedisError::String( - err_msg_json_path_doesnt_exist_with_param_or(path.get_original(), "not an array"), + Err(path_doesnt_exist_with_param_or( + path.get_original(), + "not an array", )) }, |v| Ok(v.len().unwrap().into()), @@ -1416,9 +1408,7 @@ fn json_arr_pop_legacy( let paths = find_paths(path, root, |v| v.get_type() == SelectValueType::Array)?; if paths.is_empty() { - Err(RedisError::String( - err_msg_json_path_doesnt_exist_with_param_or(path, "not an array"), - )) + Err(path_doesnt_exist_with_param_or(path, "not an array")) } else { let res = paths.into_iter().try_fold(RedisValue::Null, |_, p| { redis_key.arr_pop(p, index, |v| { @@ -1498,9 +1488,7 @@ fn json_arr_trim_legacy( let paths = find_paths(path, root, |v| v.get_type() == SelectValueType::Array)?; if paths.is_empty() { - Err(RedisError::String( - err_msg_json_path_doesnt_exist_with_param_or(path, "not an array"), - )) + Err(path_doesnt_exist_with_param_or(path, "not an array")) } else { let res = paths .into_iter() @@ -1549,9 +1537,7 @@ fn json_obj_keys_legacy(redis_key: M::ReadHolder, path: &str) -> Red .get_first(path) .map_or(Ok(RedisValue::Null), |v| match v.get_type() { SelectValueType::Object => Ok(v.keys().unwrap().collect_vec().into()), - _ => Err(RedisError::String( - err_msg_json_path_doesnt_exist_with_param_or(path, "not an object"), - )), + _ => Err(path_doesnt_exist_with_param_or(path, "not an object")), }) } @@ -1574,11 +1560,7 @@ pub fn json_obj_len(manager: M, ctx: &Context, args: Vec(redis_key: M::ReadHolder, path: &str) -> RedisResult { redis_key.get_value().and_then(|res| { res.map_or_else( - || { - Err(RedisError::String( - err_msg_json_path_doesnt_exist_with_param_or(path, "not an object"), - )) - }, + || Err(path_doesnt_exist_with_param_or(path, "not an object")), |root| { find_all_values(path, root, |v| v.get_type() == SelectValueType::Object).map(|v| { v.into_iter() diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index bf9df2c22..2ea2cac4f 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -4,8 +4,7 @@ * the Server Side Public License v1 (SSPLv1). */ -use crate::error::Error; -use crate::manager::{err_json, err_msg_json_expected, err_msg_json_path_doesnt_exist}; +use crate::manager::{err_json, expected, path_doesnt_exist}; use crate::manager::{Manager, ReadHolder, WriteHolder}; use crate::redisjson::normalize_arr_start_index; use crate::Format; @@ -37,13 +36,7 @@ fn follow_path(path: Vec, root: &mut IValue) -> Option<&mut IValue> { path.iter() .try_fold(root, |target, token| match target.destructure_mut() { DestructuredMut::Object(obj) => obj.get_mut(token.as_str()), - DestructuredMut::Array(arr) => { - let idx = token.parse::().expect(&format!( - "An array index is parsed successfully. Array = {:?}, index = {:?}", - arr, token - )); - arr.get_mut(idx) - } + DestructuredMut::Array(arr) => arr.get_mut(token.parse::().ok()?), _ => None, }) } @@ -56,11 +49,9 @@ fn follow_path(path: Vec, root: &mut IValue) -> Option<&mut IValue> { /// fn update(path: Vec, root: &mut IValue, func: F) -> RedisResult where - F: FnOnce(&mut IValue) -> Result, + F: FnOnce(&mut IValue) -> RedisResult, { - follow_path(path, root) - .map_or_else(|| Err(err_msg_json_path_doesnt_exist().into()), func) - .map_err(Into::into) + follow_path(path, root).map_or_else(|| Err(path_doesnt_exist()), func) } /// @@ -71,13 +62,7 @@ fn remove(mut path: Vec, root: &mut IValue) -> bool { follow_path(path, root) .and_then(|target| match target.destructure_mut() { DestructuredMut::Object(obj) => obj.remove(token.as_str()), - DestructuredMut::Array(arr) => { - let idx = token.parse::().expect(&format!( - "An array index is parsed successfully. Array = {:?}, index = {:?}", - arr, token - )); - arr.remove(idx) - } + DestructuredMut::Array(arr) => arr.remove(token.parse::().ok()?), _ => None, }) .is_some() @@ -86,7 +71,7 @@ fn remove(mut path: Vec, root: &mut IValue) -> bool { impl<'a> IValueKeyHolderWrite<'a> { fn do_op(&mut self, paths: Vec, op_fun: F) -> RedisResult where - F: FnOnce(&mut IValue) -> Result, + F: FnOnce(&mut IValue) -> RedisResult, { let root = self.get_value()?.unwrap(); update(paths, root, op_fun) @@ -121,12 +106,12 @@ impl<'a> IValueKeyHolderWrite<'a> { *v = IValue::from(new_val.clone()); Ok(new_val) })?; - if n.has_decimal_point() { - n.to_f64().and_then(serde_json::Number::from_f64) - } else { - n.to_i64().map(Into::into) - } - .ok_or_else(|| RedisError::Str("result is not a number")) + if n.has_decimal_point() { + n.to_f64().and_then(serde_json::Number::from_f64) + } else { + n.to_i64().map(Into::into) + } + .ok_or_else(|| RedisError::Str("result is not a number")) } else { Err(RedisError::Str("bad input number")) } @@ -234,10 +219,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { }) .unwrap_or_else(|| Err(err_json(v, "string"))) }), - _ => Err(RedisError::String(err_msg_json_expected( - "string", - val.as_str(), - ))), + _ => Err(expected("string", val.as_str())), } } @@ -265,7 +247,7 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { let len = arr.len() as _; let index = if index < 0 { len + index } else { index }; if !(0..=len).contains(&index) { - return Err("ERR index out of bounds".into()); + return Err(RedisError::Str("ERR index out of bounds")); } arr.extend(args.iter().cloned()); arr[index as _..].rotate_right(args.len()); @@ -424,7 +406,7 @@ impl<'a> Manager for RedisIValueJsonKeyManager<'a> { ctx.replicate_verbatim(); } - fn from_str(&self, val: &str, format: Format, limit_depth: bool) -> Result { + fn from_str(&self, val: &str, format: Format, limit_depth: bool) -> RedisResult { match format { Format::JSON | Format::STRING => { let mut deserializer = serde_json::Deserializer::from_str(val); @@ -433,23 +415,21 @@ impl<'a> Manager for RedisIValueJsonKeyManager<'a> { } IValue::deserialize(&mut deserializer).map_err(|e| e.into()) } - Format::BSON => from_document( - Document::from_reader(&mut Cursor::new(val.as_bytes())) - .map_err(|e| e.to_string())?, - ) - .map_err(|e| e.to_string().into()) - .and_then(|docs: Document| { - docs.iter().next().map_or(Ok(IValue::NULL), |(_, b)| { - let v: serde_json::Value = b.clone().into(); - let mut out = serde_json::Serializer::new(Vec::new()); - v.serialize(&mut out).unwrap(); - self.from_str( - &String::from_utf8(out.into_inner()).unwrap(), - Format::JSON, - limit_depth, - ) - }) - }), + Format::BSON => Document::from_reader(&mut Cursor::new(val.as_bytes())) + .and_then(from_document) + .map_err(|e| RedisError::String(e.to_string())) + .and_then(|docs: Document| { + docs.iter().next().map_or(Ok(IValue::NULL), |(_, b)| { + let v: serde_json::Value = b.clone().into(); + let mut out = serde_json::Serializer::new(Vec::new()); + v.serialize(&mut out).unwrap(); + self.from_str( + &String::from_utf8(out.into_inner()).unwrap(), + Format::JSON, + limit_depth, + ) + }) + }), } } diff --git a/redis_json/src/key_value.rs b/redis_json/src/key_value.rs index 4e3541d21..205af24c5 100644 --- a/redis_json/src/key_value.rs +++ b/redis_json/src/key_value.rs @@ -6,18 +6,14 @@ use json_path::{ json_path::JsonPathToken, select_value::{SelectValue, SelectValueType}, }; -use redis_module::{redisvalue::RedisValueKey, RedisResult, RedisValue}; +use redis_module::{redisvalue::RedisValueKey, RedisError, RedisResult, RedisValue}; use serde::Serialize; use serde_json::Value; use crate::{ commands::{prepare_paths_for_updating, FoundIndex, ObjectLen, Values}, - error::Error, formatter::{RedisJsonFormatter, ReplyFormatOptions}, - manager::{ - err_msg_json_expected, err_msg_json_path_doesnt_exist_with_param, AddUpdateInfo, - SetUpdateInfo, UpdateInfo, - }, + manager::{expected, path_doesnt_exist_with_param, AddUpdateInfo, SetUpdateInfo, UpdateInfo}, redisjson::{normalize_arr_indices, Path, ReplyFormat, SetOptions}, }; @@ -30,13 +26,12 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { KeyValue { val: v } } - pub fn get_first<'b>(&'a self, path: &'b str) -> Result<&'a V, Error> { + pub fn get_first<'b>(&'a self, path: &'b str) -> RedisResult<&'a V> { let results = self.get_values(path)?; - results.first().copied().ok_or_else(|| { - err_msg_json_path_doesnt_exist_with_param(path) - .as_str() - .into() - }) + results + .first() + .copied() + .ok_or_else(|| path_doesnt_exist_with_param(path)) } pub fn resp_serialize(&self, path: Path) -> RedisResult { @@ -88,7 +83,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } } - pub fn get_values<'b>(&'a self, path: &'b str) -> Result, Error> { + pub fn get_values<'b>(&'a self, path: &'b str) -> RedisResult> { let query = compile(path)?; Ok(calc_once(query, self.val)) } @@ -110,7 +105,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { paths: Vec, format: ReplyFormatOptions, is_legacy: bool, - ) -> Result { + ) -> RedisResult { // TODO: Creating a temp doc here duplicates memory usage. This can be very memory inefficient. // A better way would be to create a doc of references to the original doc but no current support // in serde_json. I'm going for this implementation anyway because serde_json isn't supposed to be @@ -124,9 +119,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { let value = if is_legacy { if results.is_empty() { - return Err(err_msg_json_path_doesnt_exist_with_param( - path.get_original(), - )); + return Err(path_doesnt_exist_with_param(path.get_original())); } Values::Single(results[0]) } else { @@ -158,7 +151,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { Ok(res) } - fn to_resp3(&self, paths: Vec, format: ReplyFormatOptions) -> Result { + fn to_resp3(&self, paths: Vec, format: ReplyFormatOptions) -> RedisResult { let results = paths .into_iter() .map(|path: Path| self.to_resp3_path(path, format)) @@ -177,7 +170,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { path: &str, format: ReplyFormatOptions, is_legacy: bool, - ) -> Result { + ) -> RedisResult { if is_legacy { self.to_string_single(path, format).map(Into::into) } else if format.is_resp3_reply() { @@ -235,11 +228,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } } - pub fn to_json( - &self, - paths: Vec, - format: ReplyFormatOptions, - ) -> Result { + pub fn to_json(&self, paths: Vec, format: ReplyFormatOptions) -> RedisResult { let is_legacy = paths.iter().all(Path::is_legacy); // If we're using RESP3, we need to reply with an array of values @@ -252,14 +241,14 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } } - fn find_add_paths(&mut self, path: &str) -> Result, Error> { + fn find_add_paths(&mut self, path: &str) -> RedisResult> { let mut query = compile(path)?; if !query.is_static() { - return Err("Err wrong static path".into()); + return Err(RedisError::Str("Err wrong static path")); } if query.size() < 1 { - return Err("Err path must end with object key to set".into()); + return Err(RedisError::Str("Err path must end with object key to set")); } let (last, token_type) = query.pop_last().unwrap(); @@ -293,7 +282,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { let res = calc_once_paths(query, self.val); if res.is_empty() { - Err("ERR array index out of range".into()) + Err(RedisError::Str("ERR array index out of range")) } else { Ok(Vec::new()) } @@ -301,7 +290,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } } - pub fn find_paths(&mut self, path: &str, option: SetOptions) -> Result, Error> { + pub fn find_paths(&mut self, path: &str, option: SetOptions) -> RedisResult> { if option != SetOptions::NotExists { let query = compile(path)?; let mut paths = calc_once_paths(query, self.val); @@ -323,16 +312,16 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } } - pub fn to_string_single(&self, path: &str, fmt: ReplyFormatOptions) -> Result { + pub fn to_string_single(&self, path: &str, fmt: ReplyFormatOptions) -> RedisResult { self.get_first(path).map(|o| Self::serialize_object(o, fmt)) } - pub fn to_string_multi(&self, path: &str, fmt: ReplyFormatOptions) -> Result { + pub fn to_string_multi(&self, path: &str, fmt: ReplyFormatOptions) -> RedisResult { self.get_values(path) .map(|o| Self::serialize_object(o, fmt)) } - pub fn get_type(&self, path: &str) -> Result { + pub fn get_type(&self, path: &str) -> RedisResult { self.get_first(path).map(Self::value_name).map(Into::into) } @@ -356,27 +345,20 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } } - pub fn str_len(&self, path: &str) -> Result { + pub fn str_len(&self, path: &str) -> RedisResult { let first = self.get_first(path)?; match first.get_type() { SelectValueType::String => Ok(first.get_str().len()), - _ => Err( - err_msg_json_expected("string", self.get_type(path).unwrap().as_str()) - .as_str() - .into(), - ), + _ => Err(expected("string", self.get_type(path).unwrap().as_str())), } } - pub fn obj_len(&self, path: &str) -> Result { + pub fn obj_len(&self, path: &str) -> RedisResult { self.get_first(path) .map_or(Ok(ObjectLen::NoneExisting), |first| { match first.get_type() { SelectValueType::Object => Ok(ObjectLen::Len(first.len().unwrap())), - _ => Err(Error::from(err_msg_json_expected( - "object", - self.get_type(path).unwrap().as_str(), - ))), + _ => Err(expected("object", self.get_type(path).unwrap().as_str())), } }) } @@ -408,13 +390,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } } - pub fn arr_index( - &self, - path: &str, - json_value: Value, - start: i64, - end: i64, - ) -> Result { + pub fn arr_index(&self, path: &str, json_value: Value, start: i64, end: i64) -> RedisResult { let values = self.get_values(path)?; Ok(values .into_iter() @@ -430,13 +406,10 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { json_value: Value, start: i64, end: i64, - ) -> Result { + ) -> RedisResult { let arr = self.get_first(path)?; match Self::arr_first_index_single(arr, &json_value, start, end) { - FoundIndex::NotArray => Err(Error::from(err_msg_json_expected( - "array", - self.get_type(path).unwrap().as_str(), - ))), + FoundIndex::NotArray => Err(expected("array", self.get_type(path).unwrap().as_str())), i => Ok(i.into()), } } diff --git a/redis_json/src/manager.rs b/redis_json/src/manager.rs index dbb188021..9023b7c6f 100644 --- a/redis_json/src/manager.rs +++ b/redis_json/src/manager.rs @@ -9,13 +9,10 @@ use redis_module::key::KeyFlags; use serde_json::Number; use redis_module::raw::RedisModuleKey; -use redis_module::{Context, RedisResult, RedisString}; - -use crate::Format; - -use crate::error::Error; +use redis_module::{Context, RedisError, RedisResult, RedisString}; use crate::key_value::KeyValue; +use crate::Format; pub struct SetUpdateInfo { pub path: Vec, @@ -77,30 +74,32 @@ pub trait Manager { fn open_key_write(&self, ctx: &Context, key: RedisString) -> RedisResult; fn apply_changes(&self, ctx: &Context); #[allow(clippy::wrong_self_convention)] - fn from_str(&self, val: &str, format: Format, limit_depth: bool) -> Result; + fn from_str(&self, val: &str, format: Format, limit_depth: bool) -> RedisResult; fn get_memory(&self, v: &Self::V) -> RedisResult; fn is_json(&self, key: *mut RedisModuleKey) -> RedisResult; } -pub(crate) fn err_json(value: &V, expected_value: &'static str) -> Error { - Error::from(err_msg_json_expected( - expected_value, - KeyValue::value_name(value), +pub(crate) fn err_json(value: &V, expected_value: &'static str) -> RedisError { + RedisError::String(format!( + "ERR {}", + expected(expected_value, KeyValue::value_name(value)) )) } -pub(crate) fn err_msg_json_expected(expected_value: &'static str, found: &str) -> String { - format!("WRONGTYPE wrong type of path value - expected {expected_value} but found {found}") +pub(crate) fn expected(expected_value: &'static str, found: &str) -> RedisError { + RedisError::String(format!( + "WRONGTYPE wrong type of path value - expected {expected_value} but found {found}" + )) } -pub(crate) fn err_msg_json_path_doesnt_exist_with_param(path: &str) -> String { - format!("ERR Path '{path}' does not exist") +pub(crate) fn path_doesnt_exist_with_param(path: &str) -> RedisError { + RedisError::String(format!("ERR Path '{path}' does not exist")) } -pub(crate) fn err_msg_json_path_doesnt_exist() -> String { - "ERR Path does not exist".to_string() +pub(crate) fn path_doesnt_exist() -> RedisError { + RedisError::String("ERR Path does not exist".into()) } -pub(crate) fn err_msg_json_path_doesnt_exist_with_param_or(path: &str, or: &str) -> String { - format!("ERR Path '{path}' does not exist or {or}") +pub(crate) fn path_doesnt_exist_with_param_or(path: &str, or: &str) -> RedisError { + RedisError::String(format!("ERR Path '{path}' does not exist or {or}")) } From dd1e325182d5dfba56ce302df507b7d4125a27c8 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Sun, 25 Aug 2024 18:27:30 +0300 Subject: [PATCH 29/33] PathTracker is driving me insane --- json_path/src/json_path.rs | 89 ++++++++++------------ redis_json/src/commands.rs | 29 ++++--- redis_json/src/key_value.rs | 148 ++++++++++++++---------------------- redis_json/src/manager.rs | 11 +-- 4 files changed, 116 insertions(+), 161 deletions(-) diff --git a/json_path/src/json_path.rs b/json_path/src/json_path.rs index 8304b90b0..c9928c9df 100644 --- a/json_path/src/json_path.rs +++ b/json_path/src/json_path.rs @@ -290,30 +290,28 @@ struct PathTracker<'i, 'j> { element: PathTrackerElement<'i>, } -const fn create_empty_tracker<'i, 'j>() -> PathTracker<'i, 'j> { - PathTracker { - parent: None, - element: PathTrackerElement::Root, +impl<'i, 'j> Default for PathTracker<'i, 'j> { + fn default() -> Self { + PathTracker { + parent: None, + element: PathTrackerElement::Root, + } } } -const fn create_str_tracker<'i, 'j>( - s: &'i str, - parent: &'j PathTracker<'i, 'j>, -) -> PathTracker<'i, 'j> { - PathTracker { - parent: Some(parent), - element: PathTrackerElement::Key(s), +impl<'i, 'j> PathTracker<'i, 'j> { + const fn str_tracker(s: &'i str, parent: &'j Self) -> Self { + Self { + parent: Some(parent), + element: PathTrackerElement::Key(s), + } } -} -const fn create_index_tracker<'i, 'j>( - index: usize, - parent: &'j PathTracker<'i, 'j>, -) -> PathTracker<'i, 'j> { - PathTracker { - parent: Some(parent), - element: PathTrackerElement::Index(index), + const fn index_tracker(i: usize, parent: &'j Self) -> Self { + Self { + parent: Some(parent), + element: PathTrackerElement::Index(i), + } } } @@ -334,10 +332,7 @@ impl<'i, 'j, S: SelectValue> PartialEq for TermEvaluationResult<'i, 'j, S> { fn eq(&self, s: &Self) -> bool { match (self, s) { (TermEvaluationResult::Value(v1), TermEvaluationResult::Value(v2)) => v1 == v2, - (_, _) => match self.partial_cmp(s) { - Some(o) => o.is_eq(), - None => false, - }, + _ => self.partial_cmp(s).map_or(false, Ordering::is_eq), } } } @@ -393,7 +388,7 @@ impl<'i, 'j, S: SelectValue> PartialOrd for TermEvaluationResult<'i, 'j, S> { SelectValueType::Null => self.partial_cmp(&TermEvaluationResult::Null), _ => None, }, - (_, _) => None, + _ => None, } } } @@ -416,10 +411,10 @@ impl<'i, 'j, S: SelectValue> TermEvaluationResult<'i, 'j, S> { (SelectValueType::String, SelectValueType::String) => { Self::re_is_match(v2.as_str(), v1.as_str()) } - (_, _) => false, + _ => false, } } - (_, _) => false, + _ => false, } } } @@ -475,27 +470,25 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { match json.get_type() { SelectValueType::Object => { if let Some(pt) = path_tracker { - let items = json.items().unwrap(); - for (key, val) in items { + json.items().unwrap().for_each(|(key, val)| { self.calc_internal( pairs.clone(), val, - Some(create_str_tracker(key, &pt)), + Some(PathTracker::str_tracker(key, &pt)), calc_data, ); self.calc_full_scan( pairs.clone(), val, - Some(create_str_tracker(key, &pt)), + Some(PathTracker::str_tracker(key, &pt)), calc_data, ); - } + }) } else { - let values = json.values().unwrap(); - for v in values { + json.values().unwrap().for_each(|v| { self.calc_internal(pairs.clone(), v, None, calc_data); self.calc_full_scan(pairs.clone(), v, None, calc_data); - } + }) } } SelectValueType::Array => { @@ -505,13 +498,13 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { self.calc_internal( pairs.clone(), v, - Some(create_index_tracker(i, &pt)), + Some(PathTracker::index_tracker(i, &pt)), calc_data, ); self.calc_full_scan( pairs.clone(), v, - Some(create_index_tracker(i, &pt)), + Some(PathTracker::index_tracker(i, &pt)), calc_data, ); } @@ -538,7 +531,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { if let Some(pt) = path_tracker { let items = json.items().unwrap(); for (key, val) in items { - let new_tracker = Some(create_str_tracker(key, &pt)); + let new_tracker = Some(PathTracker::str_tracker(key, &pt)); self.calc_internal(pairs.clone(), val, new_tracker, calc_data); } } else { @@ -552,7 +545,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { let values = json.values().unwrap(); if let Some(pt) = path_tracker { for (i, v) in values.enumerate() { - let new_tracker = Some(create_index_tracker(i, &pt)); + let new_tracker = Some(PathTracker::index_tracker(i, &pt)); self.calc_internal(pairs.clone(), v, new_tracker, calc_data); } } else { @@ -576,7 +569,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { let curr_val = json.get_key(curr.as_str()); if let Some(e) = curr_val { if let Some(pt) = path_tracker { - let new_tracker = Some(create_str_tracker(curr.as_str(), &pt)); + let new_tracker = Some(PathTracker::str_tracker(curr.as_str(), &pt)); self.calc_internal(pairs, e, new_tracker, calc_data); } else { self.calc_internal(pairs, e, None, calc_data); @@ -606,7 +599,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { _ => panic!("{c:?}"), }; if let Some(e) = curr_val { - let new_tracker = Some(create_str_tracker(s, &pt)); + let new_tracker = Some(PathTracker::str_tracker(s, &pt)); self.calc_internal(pairs.clone(), e, new_tracker, calc_data); } } @@ -655,7 +648,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { let i = Self::calc_abs_index(c.as_str().parse::().unwrap(), n); let curr_val = json.get_index(i); if let Some(e) = curr_val { - let new_tracker = Some(create_index_tracker(i, &pt)); + let new_tracker = Some(PathTracker::index_tracker(i, &pt)); self.calc_internal(pairs.clone(), e, new_tracker, calc_data); } } @@ -729,7 +722,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { for i in (start..end).step_by(step) { let curr_val = json.get_index(i); if let Some(e) = curr_val { - let new_tracker = Some(create_index_tracker(i, &pt)); + let new_tracker = Some(PathTracker::index_tracker(i, &pt)); self.calc_internal(pairs.clone(), e, new_tracker, calc_data); } } @@ -937,9 +930,10 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { self.calc_range(pairs, curr, json, path_tracker, calc_data); } Rule::filter => { - if json.get_type() == SelectValueType::Array - || json.get_type() == SelectValueType::Object - { + if matches!( + json.get_type(), + SelectValueType::Array | SelectValueType::Object + ) { /* lets expend the array, this is how most json path engines work. * Personally, I think this if should not exists. */ let values = json.values().unwrap(); @@ -953,11 +947,10 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { trace!("calc_internal v {:?}", &v); if self.evaluate_filter(curr.clone().into_inner(), v, calc_data) { - let new_tracker = Some(create_index_tracker(i, &pt)); self.calc_internal( pairs.clone(), v, - new_tracker, + Some(PathTracker::index_tracker(i, &pt)), calc_data, ); } @@ -1012,7 +1005,7 @@ impl<'i, UPTG: UserPathTrackerGenerator> PathCalculator<'i, UPTG> { root: json, }; if self.tracker_generator.is_some() { - self.calc_internal(root, json, Some(create_empty_tracker()), &mut calc_data); + self.calc_internal(root, json, Some(PathTracker::default()), &mut calc_data); } else { self.calc_internal(root, json, None, &mut calc_data); } diff --git a/redis_json/src/commands.rs b/redis_json/src/commands.rs index e0e24ff31..7c658b4a3 100644 --- a/redis_json/src/commands.rs +++ b/redis_json/src/commands.rs @@ -626,23 +626,21 @@ pub fn json_type(manager: M, ctx: &Context, args: Vec) fn json_type_impl(redis_key: &M::ReadHolder, path: &str) -> RedisResult { redis_key.get_value()?.map_or(Ok(RedisValue::Null), |root| { - let value = KeyValue::new(root) + Ok(KeyValue::new(root) .get_values(path)? .into_iter() - .map(|v| RedisValue::from(KeyValue::value_name(v))) + .map(KeyValue::value_name) + .map(RedisValue::from) .collect_vec() - .into(); - Ok(value) + .into()) }) } fn json_type_legacy(redis_key: &M::ReadHolder, path: &str) -> RedisResult { - let value = redis_key + Ok(redis_key .get_value()? - .map(|doc| KeyValue::new(doc).get_type(path).map(|s| s.into()).ok()) - .flatten() - .unwrap_or(RedisValue::Null); - Ok(value) + .and_then(|doc| KeyValue::new(doc).get_type(path).ok()) + .map_or(RedisValue::Null, Into::into)) } enum NumOp { @@ -673,9 +671,9 @@ fn json_num_op( .map(|v| { v.map_or(RedisValue::Null, |v| { if let Some(i) = v.as_i64() { - RedisValue::Integer(i) + i.into() } else { - RedisValue::Float(v.as_f64().unwrap_or_default()) + v.as_f64().unwrap_or_default().into() } }) }) @@ -992,10 +990,9 @@ fn json_str_len_impl(redis_key: &M::ReadHolder, path: &str) -> Redis } fn json_str_len_legacy(redis_key: &M::ReadHolder, path: &str) -> RedisResult { - match redis_key.get_value()? { - Some(doc) => Ok(RedisValue::Integer(KeyValue::new(doc).str_len(path)? as i64)), - None => Ok(RedisValue::Null), - } + redis_key.get_value()?.map_or(Ok(RedisValue::Null), |doc| { + KeyValue::new(doc).str_len(path).map(Into::into) + }) } /// @@ -1576,7 +1573,7 @@ fn json_obj_len_impl(redis_key: M::ReadHolder, path: &str) -> RedisR fn json_obj_len_legacy(redis_key: M::ReadHolder, path: &str) -> RedisResult { match redis_key.get_value()? { Some(doc) => match KeyValue::new(doc).obj_len(path)? { - ObjectLen::Len(l) => Ok(RedisValue::Integer(l as i64)), + ObjectLen::Len(l) => Ok(l.into()), _ => Ok(RedisValue::Null), }, None => Ok(RedisValue::Null), diff --git a/redis_json/src/key_value.rs b/redis_json/src/key_value.rs index 205af24c5..21799cab1 100644 --- a/redis_json/src/key_value.rs +++ b/redis_json/src/key_value.rs @@ -13,7 +13,7 @@ use serde_json::Value; use crate::{ commands::{prepare_paths_for_updating, FoundIndex, ObjectLen, Values}, formatter::{RedisJsonFormatter, ReplyFormatOptions}, - manager::{expected, path_doesnt_exist_with_param, AddUpdateInfo, SetUpdateInfo, UpdateInfo}, + manager::{err_json, path_doesnt_exist_with_param, AddUpdateInfo, SetUpdateInfo, UpdateInfo}, redisjson::{normalize_arr_indices, Path, ReplyFormat, SetOptions}, }; @@ -50,36 +50,25 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { fn resp_serialize_inner(v: &V) -> RedisValue { match v.get_type() { SelectValueType::Null => RedisValue::Null, - SelectValueType::Bool => match v.get_bool() { - true => RedisValue::SimpleString("true".to_string()), - false => RedisValue::SimpleString("false".to_string()), + true => RedisValue::SimpleStringStatic("true"), + false => RedisValue::SimpleStringStatic("false"), }, - - SelectValueType::Long => RedisValue::Integer(v.get_long()), - - SelectValueType::Double => RedisValue::Float(v.get_double()), - - SelectValueType::String => RedisValue::BulkString(v.get_str()), - - SelectValueType::Array => { - let res = std::iter::once(RedisValue::SimpleStringStatic("[")) - .chain(v.values().unwrap().map(Self::resp_serialize_inner)) - .collect(); - RedisValue::Array(res) - } - - SelectValueType::Object => { - let res = std::iter::once(RedisValue::SimpleStringStatic("{")) - .chain(v.items().unwrap().flat_map(|(k, v)| { - [ - RedisValue::BulkString(k.to_string()), - Self::resp_serialize_inner(v), - ] - })) - .collect(); - RedisValue::Array(res) - } + SelectValueType::Long => v.get_long().into(), + SelectValueType::Double => v.get_double().into(), + SelectValueType::String => v.get_str().into(), + SelectValueType::Array => std::iter::once(RedisValue::SimpleStringStatic("[")) + .chain(v.values().unwrap().map(Self::resp_serialize_inner)) + .collect_vec() + .into(), + SelectValueType::Object => std::iter::once(RedisValue::SimpleStringStatic("{")) + .chain( + v.items() + .unwrap() + .flat_map(|(k, v)| [k.to_string().into(), Self::resp_serialize_inner(v)]), + ) + .collect_vec() + .into(), } } @@ -133,18 +122,17 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { // If we're using RESP3, we need to convert the HashMap to a RedisValue::Map unless we're using the legacy format let res = if format.is_resp3_reply() { - let map = temp_doc + temp_doc .into_iter() - .map(|(k, v)| { - let key = RedisValueKey::String(k.to_string()); + .map(|(key, v)| { let value = match v { Values::Single(value) => Self::value_to_resp3(value, format), Values::Multi(values) => Self::values_to_resp3(values, format), }; - (key, value) + (key.into(), value) }) - .collect(); - RedisValue::Map(map) + .collect::>() + .into() } else { Self::serialize_object(temp_doc, format).into() }; @@ -152,11 +140,11 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } fn to_resp3(&self, paths: Vec, format: ReplyFormatOptions) -> RedisResult { - let results = paths + Ok(paths .into_iter() - .map(|path: Path| self.to_resp3_path(path, format)) - .collect(); - Ok(RedisValue::Array(results)) + .map(|path| self.to_resp3_path(path, format)) + .collect_vec() + .into()) } pub fn to_resp3_path(&self, path: Path, format: ReplyFormatOptions) -> RedisValue { @@ -190,41 +178,27 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } pub fn value_to_resp3(value: &V, format: ReplyFormatOptions) -> RedisValue { - if format.format == ReplyFormat::EXPAND { - match value.get_type() { - SelectValueType::Null => RedisValue::Null, - SelectValueType::Bool => RedisValue::Bool(value.get_bool()), - SelectValueType::Long => RedisValue::Integer(value.get_long()), - SelectValueType::Double => RedisValue::Float(value.get_double()), - SelectValueType::String => RedisValue::BulkString(value.get_str()), - SelectValueType::Array => RedisValue::Array( - value - .values() - .unwrap() - .map(|v| Self::value_to_resp3(v, format)) - .collect(), - ), - SelectValueType::Object => RedisValue::Map( - value - .items() - .unwrap() - .map(|(k, v)| { - ( - RedisValueKey::String(k.to_string()), - Self::value_to_resp3(v, format), - ) - }) - .collect(), - ), - } - } else { - match value.get_type() { - SelectValueType::Null => RedisValue::Null, - SelectValueType::Bool => RedisValue::Bool(value.get_bool()), - SelectValueType::Long => RedisValue::Integer(value.get_long()), - SelectValueType::Double => RedisValue::Float(value.get_double()), - _ => RedisValue::BulkString(Self::serialize_object(value, format)), + match value.get_type() { + SelectValueType::Null => RedisValue::Null, + SelectValueType::Bool => value.get_bool().into(), + SelectValueType::Long => value.get_long().into(), + SelectValueType::Double => value.get_double().into(), + SelectValueType::String if format.format == ReplyFormat::EXPAND => { + value.get_str().into() } + SelectValueType::Array if format.format == ReplyFormat::EXPAND => value + .values() + .unwrap() + .map(|value| Self::value_to_resp3(value, format)) + .collect_vec() + .into(), + SelectValueType::Object if format.format == ReplyFormat::EXPAND => value + .items() + .unwrap() + .map(|(key, value)| (key.into(), Self::value_to_resp3(value, format))) + .collect::>() + .into(), + _ => Self::serialize_object(value, format).into(), } } @@ -321,11 +295,11 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { .map(|o| Self::serialize_object(o, fmt)) } - pub fn get_type(&self, path: &str) -> RedisResult { - self.get_first(path).map(Self::value_name).map(Into::into) + pub fn get_type(&self, path: &str) -> RedisResult<&'static str> { + self.get_first(path).map(Self::value_name) } - pub fn value_name(value: &V) -> &str { + pub fn value_name(value: &V) -> &'static str { match value.get_type() { SelectValueType::Null => "null", SelectValueType::Bool => "boolean", @@ -349,7 +323,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { let first = self.get_first(path)?; match first.get_type() { SelectValueType::String => Ok(first.get_str().len()), - _ => Err(expected("string", self.get_type(path).unwrap().as_str())), + _ => Err(err_json(first, "string")), } } @@ -358,7 +332,7 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { .map_or(Ok(ObjectLen::NoneExisting), |first| { match first.get_type() { SelectValueType::Object => Ok(ObjectLen::Len(first.len().unwrap())), - _ => Err(expected("object", self.get_type(path).unwrap().as_str())), + _ => Err(err_json(first, "object")), } }) } @@ -390,26 +364,20 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } } - pub fn arr_index(&self, path: &str, json_value: Value, start: i64, end: i64) -> RedisResult { + pub fn arr_index(&self, path: &str, v: Value, start: i64, end: i64) -> RedisResult { let values = self.get_values(path)?; Ok(values .into_iter() - .map(|value| Self::arr_first_index_single(value, &json_value, start, end)) + .map(|arr| Self::arr_first_index_single(arr, &v, start, end)) .map(RedisValue::from) .collect_vec() .into()) } - pub fn arr_index_legacy( - &self, - path: &str, - json_value: Value, - start: i64, - end: i64, - ) -> RedisResult { - let arr = self.get_first(path)?; - match Self::arr_first_index_single(arr, &json_value, start, end) { - FoundIndex::NotArray => Err(expected("array", self.get_type(path).unwrap().as_str())), + pub fn arr_index_legacy(&self, path: &str, v: Value, start: i64, end: i64) -> RedisResult { + let first = self.get_first(path)?; + match Self::arr_first_index_single(first, &v, start, end) { + FoundIndex::NotArray => Err(err_json(first, "array")), i => Ok(i.into()), } } diff --git a/redis_json/src/manager.rs b/redis_json/src/manager.rs index 9023b7c6f..5073fac41 100644 --- a/redis_json/src/manager.rs +++ b/redis_json/src/manager.rs @@ -79,16 +79,13 @@ pub trait Manager { fn is_json(&self, key: *mut RedisModuleKey) -> RedisResult; } -pub(crate) fn err_json(value: &V, expected_value: &'static str) -> RedisError { - RedisError::String(format!( - "ERR {}", - expected(expected_value, KeyValue::value_name(value)) - )) +pub(crate) fn err_json(value: &V, exp: &'static str) -> RedisError { + expected(exp, KeyValue::value_name(value)) } -pub(crate) fn expected(expected_value: &'static str, found: &str) -> RedisError { +pub(crate) fn expected(exp: &'static str, found: &str) -> RedisError { RedisError::String(format!( - "WRONGTYPE wrong type of path value - expected {expected_value} but found {found}" + "ERR WRONGTYPE wrong type of path value - expected {exp} but found {found}" )) } From a78b5b621c005dfdf4657ca39d9807509da39c59 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Mon, 26 Aug 2024 15:12:52 +0300 Subject: [PATCH 30/33] . --- json_path/src/json_node.rs | 12 +-- json_path/src/json_path.rs | 133 +++++++++++-------------------- json_path/src/select_value.rs | 2 +- redis_json/src/ivalue_manager.rs | 62 ++++++-------- redis_json/src/key_value.rs | 22 +++-- 5 files changed, 89 insertions(+), 142 deletions(-) diff --git a/json_path/src/json_node.rs b/json_path/src/json_node.rs index ca7bffa10..42a8066c4 100644 --- a/json_path/src/json_node.rs +++ b/json_path/src/json_node.rs @@ -6,7 +6,7 @@ /// Use `SelectValue` use crate::select_value::{SelectValue, SelectValueType}; -use ijson::{IValue, ValueType}; +use ijson::{INumber, IValue, ValueType}; use serde_json::Value; impl SelectValue for Value { @@ -86,10 +86,10 @@ impl SelectValue for Value { matches!(self, Self::Array(_)) } - fn is_double(&self) -> Option { + fn is_double(&self) -> bool { match self { - Self::Number(num) => Some(num.is_f64()), - _ => None, + Self::Number(num) => num.is_f64(), + _ => false, } } @@ -194,8 +194,8 @@ impl SelectValue for IValue { self.is_array() } - fn is_double(&self) -> Option { - Some(self.as_number()?.has_decimal_point()) + fn is_double(&self) -> bool { + self.as_number().map_or(false, INumber::has_decimal_point) } fn get_str(&self) -> String { diff --git a/json_path/src/json_path.rs b/json_path/src/json_path.rs index c9928c9df..87787a2d7 100644 --- a/json_path/src/json_path.rs +++ b/json_path/src/json_path.rs @@ -67,11 +67,10 @@ impl<'i> Query<'i> { /// Example: $.foo.bar has 2 elements #[allow(dead_code)] pub fn size(&mut self) -> usize { - if self.size.is_some() { - return self.size.unwrap(); - } - self.is_static(); - self.size() + self.size.unwrap_or_else(|| { + self.is_static(); + self.size() + }) } /// Results whether or not the compiled json path is static @@ -81,28 +80,16 @@ impl<'i> Query<'i> { /// none static path: $.*.bar #[allow(dead_code)] pub fn is_static(&mut self) -> bool { - if self.is_static.is_some() { - return self.is_static.unwrap(); - } - let mut size = 0; - let mut is_static = true; - let root_copy = self.root.clone(); - for n in root_copy { - size += 1; - match n.as_rule() { - Rule::literal | Rule::number => continue, - Rule::numbers_list | Rule::string_list => { - let inner = n.into_inner(); - if inner.count() > 1 { - is_static = false; - } - } - _ => is_static = false, - } - } - self.size = Some(size); - self.is_static = Some(is_static); - self.is_static() + self.is_static.unwrap_or_else(|| { + self.size = Some(self.root.len()); + let root_copy = self.root.clone(); + self.is_static = Some(root_copy.fold(true, |is_static, n| match n.as_rule() { + Rule::literal | Rule::number => is_static, + Rule::numbers_list | Rule::string_list => is_static && n.into_inner().count() <= 1, + _ => false, + })); + self.is_static() + }) } } @@ -339,53 +326,33 @@ impl<'i, 'j, S: SelectValue> PartialEq for TermEvaluationResult<'i, 'j, S> { impl<'i, 'j, S: SelectValue> PartialOrd for TermEvaluationResult<'i, 'j, S> { fn partial_cmp(&self, s: &Self) -> Option { + use SelectValueType as SVT; + use TermEvaluationResult as TER; match (self, s) { - (TermEvaluationResult::Integer(n1), TermEvaluationResult::Integer(n2)) => { - Some(n1.cmp(n2)) - } - (TermEvaluationResult::Float(_), TermEvaluationResult::Integer(n2)) => { - self.partial_cmp(&TermEvaluationResult::Float(*n2 as f64)) - } - (TermEvaluationResult::Integer(n1), TermEvaluationResult::Float(_)) => { - TermEvaluationResult::Float(*n1 as f64).partial_cmp(s) - } - (TermEvaluationResult::Float(f1), TermEvaluationResult::Float(f2)) => { - f1.partial_cmp(f2) - } - (TermEvaluationResult::Str(s1), TermEvaluationResult::Str(s2)) => Some(s1.cmp(s2)), - (TermEvaluationResult::Str(s1), TermEvaluationResult::String(s2)) => { - Some((*s1).cmp(s2)) - } - (TermEvaluationResult::String(s1), TermEvaluationResult::Str(s2)) => { - Some((s1[..]).cmp(s2)) - } - (TermEvaluationResult::String(s1), TermEvaluationResult::String(s2)) => { - Some(s1.cmp(s2)) - } - (TermEvaluationResult::Bool(b1), TermEvaluationResult::Bool(b2)) => Some(b1.cmp(b2)), - (TermEvaluationResult::Null, TermEvaluationResult::Null) => Some(Ordering::Equal), - (TermEvaluationResult::Value(v), _) => match v.get_type() { - SelectValueType::Long => TermEvaluationResult::Integer(v.get_long()).partial_cmp(s), - SelectValueType::Double => { - TermEvaluationResult::Float(v.get_double()).partial_cmp(s) - } - SelectValueType::String => TermEvaluationResult::Str(v.as_str()).partial_cmp(s), - SelectValueType::Bool => TermEvaluationResult::Bool(v.get_bool()).partial_cmp(s), - SelectValueType::Null => TermEvaluationResult::Null.partial_cmp(s), + (TER::Integer(n1), TER::Integer(n2)) => n1.partial_cmp(n2), + (TER::Float(_), TER::Integer(n2)) => self.partial_cmp(&TER::Float(*n2 as _)), + (TER::Integer(n1), TER::Float(_)) => TER::Float(*n1 as _).partial_cmp(s), + (TER::Float(f1), TER::Float(f2)) => f1.partial_cmp(f2), + (TER::Str(s1), TER::Str(s2)) => s1.partial_cmp(s2), + (TER::Str(s1), TER::String(s2)) => (*s1).partial_cmp(s2), + (TER::String(s1), TER::Str(s2)) => s1[..].partial_cmp(s2), + (TER::String(s1), TER::String(s2)) => s1.partial_cmp(s2), + (TER::Bool(b1), TER::Bool(b2)) => b1.partial_cmp(b2), + (TER::Null, TER::Null) => Some(Ordering::Equal), + (TER::Value(v), _) => match v.get_type() { + SVT::Long => TER::Integer(v.get_long()).partial_cmp(s), + SVT::Double => TER::Float(v.get_double()).partial_cmp(s), + SVT::String => TER::Str(v.as_str()).partial_cmp(s), + SVT::Bool => TER::Bool(v.get_bool()).partial_cmp(s), + SVT::Null => TER::Null.partial_cmp(s), _ => None, }, - (_, TermEvaluationResult::Value(v)) => match v.get_type() { - SelectValueType::Long => { - self.partial_cmp(&TermEvaluationResult::Integer(v.get_long())) - } - SelectValueType::Double => { - self.partial_cmp(&TermEvaluationResult::Float(v.get_double())) - } - SelectValueType::String => self.partial_cmp(&TermEvaluationResult::Str(v.as_str())), - SelectValueType::Bool => { - self.partial_cmp(&TermEvaluationResult::Bool(v.get_bool())) - } - SelectValueType::Null => self.partial_cmp(&TermEvaluationResult::Null), + (_, TER::Value(v)) => match v.get_type() { + SVT::Long => self.partial_cmp(&TER::Integer(v.get_long())), + SVT::Double => self.partial_cmp(&TER::Float(v.get_double())), + SVT::String => self.partial_cmp(&TER::Str(v.as_str())), + SVT::Bool => self.partial_cmp(&TER::Bool(v.get_bool())), + SVT::Null => self.partial_cmp(&TER::Null), _ => None, }, _ => None, @@ -399,21 +366,17 @@ impl<'i, 'j, S: SelectValue> TermEvaluationResult<'i, 'j, S> { } fn re(&self, s: &Self) -> bool { + use SelectValueType as SVT; + use TermEvaluationResult as TER; match (self, s) { - (TermEvaluationResult::Value(v), TermEvaluationResult::Str(regex)) => { - match v.get_type() { - SelectValueType::String => Self::re_is_match(regex, v.as_str()), - _ => false, - } - } - (TermEvaluationResult::Value(v1), TermEvaluationResult::Value(v2)) => { - match (v1.get_type(), v2.get_type()) { - (SelectValueType::String, SelectValueType::String) => { - Self::re_is_match(v2.as_str(), v1.as_str()) - } - _ => false, - } - } + (TER::Value(v), TER::Str(regex)) => match v.get_type() { + SVT::String => Self::re_is_match(regex, v.as_str()), + _ => false, + }, + (TER::Value(v1), TER::Value(v2)) => match (v1.get_type(), v2.get_type()) { + (SVT::String, SVT::String) => Self::re_is_match(v2.as_str(), v1.as_str()), + _ => false, + }, _ => false, } } diff --git a/json_path/src/select_value.rs b/json_path/src/select_value.rs index 86d61e8bb..9823a678a 100644 --- a/json_path/src/select_value.rs +++ b/json_path/src/select_value.rs @@ -29,7 +29,7 @@ pub trait SelectValue: Debug + Eq + PartialEq + Default + Clone + Serialize { fn get_key<'a>(&'a self, key: &str) -> Option<&'a Self>; fn get_index(&self, index: usize) -> Option<&Self>; fn is_array(&self) -> bool; - fn is_double(&self) -> Option; + fn is_double(&self) -> bool; fn get_str(&self) -> String; fn as_str(&self) -> &str; diff --git a/redis_json/src/ivalue_manager.rs b/redis_json/src/ivalue_manager.rs index 2ea2cac4f..7d87d9190 100644 --- a/redis_json/src/ivalue_manager.rs +++ b/redis_json/src/ivalue_manager.rs @@ -10,7 +10,7 @@ use crate::redisjson::normalize_arr_start_index; use crate::Format; use crate::REDIS_JSON_TYPE; use bson::{from_document, Document}; -use ijson::{DestructuredMut, INumber, IObject, IString, IValue, ValueType}; +use ijson::{DestructuredMut, DestructuredRef, INumber, IObject, IString, IValue}; use json_path::select_value::{SelectValue, SelectValueType}; use redis_module::key::{verify_type, KeyFlags, RedisKey, RedisKeyWritable}; use redis_module::raw::{RedisModuleKey, Status}; @@ -33,7 +33,7 @@ pub struct IValueKeyHolderWrite<'a> { } fn follow_path(path: Vec, root: &mut IValue) -> Option<&mut IValue> { - path.iter() + path.into_iter() .try_fold(root, |target, token| match target.destructure_mut() { DestructuredMut::Object(obj) => obj.get_mut(token.as_str()), DestructuredMut::Array(arr) => arr.get_mut(token.parse::().ok()?), @@ -234,23 +234,18 @@ impl<'a> WriteHolder for IValueKeyHolderWrite<'a> { }) } - fn arr_insert( - &mut self, - paths: Vec, - args: &[IValue], - index: i64, - ) -> RedisResult { + fn arr_insert(&mut self, paths: Vec, args: &[IValue], idx: i64) -> RedisResult { self.do_op(paths, |v| { v.as_array_mut() .map(|arr| { // Verify legal index in bounds let len = arr.len() as _; - let index = if index < 0 { len + index } else { index }; - if !(0..=len).contains(&index) { + let idx = if idx < 0 { len + idx } else { idx }; + if !(0..=len).contains(&idx) { return Err(RedisError::Str("ERR index out of bounds")); } arr.extend(args.iter().cloned()); - arr[index as _..].rotate_right(args.len()); + arr[idx as _..].rotate_right(args.len()); Ok(arr.len()) }) .unwrap_or_else(|| Err(err_json(v, "array"))) @@ -438,49 +433,40 @@ impl<'a> Manager for RedisIValueJsonKeyManager<'a> { /// fn get_memory(&self, v: &Self::V) -> RedisResult { let res = size_of::() - + match v.type_() { - ValueType::Null | ValueType::Bool => 0, - ValueType::Number => { - let num = v.as_number().unwrap(); + + match v.destructure_ref() { + DestructuredRef::Null | DestructuredRef::Bool(_) => 0, + DestructuredRef::Number(num) => { if num.has_decimal_point() { - // 64bit float - 16 + 16 // 64bit float } else if num >= &INumber::from(-128) && num <= &INumber::from(383) { - // 8bit - 0 + 0 // 8bit } else if num > &INumber::from(-8_388_608) && num <= &INumber::from(8_388_607) { - // 24bit - 4 + 4 // 24bit } else { - // 64bit - 16 + 16 // 64bit } } - ValueType::String => v.as_string().unwrap().len(), - ValueType::Array => { - let arr = v.as_array().unwrap(); + DestructuredRef::String(str) => str.len(), + DestructuredRef::Array(arr) => { let capacity = arr.capacity(); if capacity == 0 { 0 } else { - size_of::() * (capacity + 2) - + arr - .into_iter() - .map(|v| self.get_memory(v).unwrap()) - .sum::() + arr.into_iter() + .fold(size_of::() * (capacity + 2), |acc, v| { + acc + self.get_memory(v).unwrap() + }) } } - ValueType::Object => { - let val = v.as_object().unwrap(); + DestructuredRef::Object(val) => { let capacity = val.capacity(); if capacity == 0 { 0 } else { - size_of::() * (capacity * 3 + 2) - + val - .into_iter() - .map(|(s, v)| s.len() + self.get_memory(v).unwrap()) - .sum::() + val.into_iter() + .fold(size_of::() * (capacity * 3 + 2), |acc, (s, v)| { + acc + s.len() + self.get_memory(v).unwrap() + }) } } }; diff --git a/redis_json/src/key_value.rs b/redis_json/src/key_value.rs index 21799cab1..5d327f375 100644 --- a/redis_json/src/key_value.rs +++ b/redis_json/src/key_value.rs @@ -178,21 +178,20 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { } pub fn value_to_resp3(value: &V, format: ReplyFormatOptions) -> RedisValue { + use SelectValueType as SVT; match value.get_type() { - SelectValueType::Null => RedisValue::Null, - SelectValueType::Bool => value.get_bool().into(), - SelectValueType::Long => value.get_long().into(), - SelectValueType::Double => value.get_double().into(), - SelectValueType::String if format.format == ReplyFormat::EXPAND => { - value.get_str().into() - } - SelectValueType::Array if format.format == ReplyFormat::EXPAND => value + SVT::Null => RedisValue::Null, + SVT::Bool => value.get_bool().into(), + SVT::Long => value.get_long().into(), + SVT::Double => value.get_double().into(), + SVT::String if format.format == ReplyFormat::EXPAND => value.get_str().into(), + SVT::Array if format.format == ReplyFormat::EXPAND => value .values() .unwrap() .map(|value| Self::value_to_resp3(value, format)) .collect_vec() .into(), - SelectValueType::Object if format.format == ReplyFormat::EXPAND => value + SVT::Object if format.format == ReplyFormat::EXPAND => value .items() .unwrap() .map(|(key, value)| (key.into(), Self::value_to_resp3(value, format))) @@ -309,9 +308,8 @@ impl<'a, V: SelectValue + 'a> KeyValue<'a, V> { // incorrect casts. However when querying the type of such a value, // any response other than 'integer' is a breaking change SelectValueType::Double => match value.is_double() { - Some(true) => "number", - Some(false) => "integer", - _ => unreachable!(), + true => "number", + false => "integer", }, SelectValueType::String => "string", SelectValueType::Array => "array", From db1ac87da8c63c72d98965ffe13071c2a5fef666 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Mon, 26 Aug 2024 15:27:40 +0300 Subject: [PATCH 31/33] remove test from refactoring --- tests/pytest/test.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/pytest/test.py b/tests/pytest/test.py index 0f748f9d1..515fcd06a 100644 --- a/tests/pytest/test.py +++ b/tests/pytest/test.py @@ -1533,9 +1533,6 @@ def test_recursive_descent(env): r.expect('JSON.SET', 'k', '$', '[{"a":1}]').ok() r.expect('JSON.SET', 'k', '$..*', '[{"a":1}]').ok() r.expect('JSON.GET', 'k', '$').equal('[[[{"a":1}]]]') - - r.expect('JSON.SET', 'k', '$', '[1]').ok() - r.expect('JSON.GET', 'k', '$..[?@>0]').equal('[1]') # class CacheTestCase(BaseReJSONTest): # @property From 93a4510b112aa18a4c08761e61f05a1dc9561977 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Mon, 26 Aug 2024 15:54:57 +0300 Subject: [PATCH 32/33] removing dead loc to make codecov happy --- redis_json/src/error.rs | 22 +--------------------- redis_json/src/redisjson.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 28 deletions(-) diff --git a/redis_json/src/error.rs b/redis_json/src/error.rs index b7113d4d9..b308f2349 100644 --- a/redis_json/src/error.rs +++ b/redis_json/src/error.rs @@ -4,8 +4,6 @@ * the Server Side Public License v1 (SSPLv1). */ -use json_path::json_path::QueryCompilationError; -use redis_module::RedisError; use std::num::ParseIntError; #[derive(Debug)] @@ -19,12 +17,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: QueryCompilationError) -> Self { - Self { msg: e.to_string() } - } -} - impl From for Error { fn from(err: redis_module::error::GenericError) -> Self { err.to_string().into() @@ -53,18 +45,12 @@ impl From for Error { } } -impl From for Error { - fn from(e: RedisError) -> Self { - Self::from(format!("ERR {e}")) - } -} - impl From<&str> for Error { fn from(e: &str) -> Self { Self { msg: e.to_string() } } } - + impl From for Error { fn from(e: serde_json::Error) -> Self { Self { msg: e.to_string() } @@ -106,12 +92,6 @@ mod tests { assert_eq!(err.msg, "invalid digit found in string"); } - #[test] - fn test_from_redis_error() { - let err: Error = RedisError::short_read().into(); - assert_eq!(err.msg, "ERR ERR short read or OOM loading DB"); - } - #[test] fn test_from_error() { let err: Error = redis_module::error::Error::generic("error from Generic").into(); diff --git a/redis_json/src/redisjson.rs b/redis_json/src/redisjson.rs index 5ccb13ace..9c649bf0e 100644 --- a/redis_json/src/redisjson.rs +++ b/redis_json/src/redisjson.rs @@ -10,12 +10,11 @@ // User-provided JSON is converted to a tree. This tree is stored transparently in Redis. // It can be operated on (e.g. INCR) and serialized back to JSON. -use redis_module::raw; +use redis_module::{raw, RedisResult, RedisError}; use std::os::raw::{c_int, c_void}; use crate::backward; -use crate::error::Error; use crate::ivalue_manager::RedisIValueJsonKeyManager; use crate::manager::Manager; use once_cell::unsync::Lazy; @@ -68,14 +67,14 @@ pub enum Format { BSON, } impl FromStr for Format { - type Err = Error; + type Err = RedisError; fn from_str(s: &str) -> Result { match s { "STRING" => Ok(Self::STRING), "JSON" => Ok(Self::JSON), "BSON" => Ok(Self::BSON), - _ => Err("ERR wrong format".into()), + _ => Err(RedisError::Str("ERR wrong format")), } } } @@ -88,7 +87,7 @@ pub enum ReplyFormat { EXPAND, } impl FromStr for ReplyFormat { - type Err = Error; + type Err = RedisError; fn from_str(s: &str) -> Result { match s { @@ -96,7 +95,7 @@ impl FromStr for ReplyFormat { "STRINGS" => Ok(Self::STRINGS), "EXPAND1" => Ok(Self::EXPAND1), "EXPAND" => Ok(Self::EXPAND), - _ => Err("ERR wrong reply format".into()), + _ => Err(RedisError::Str("ERR wrong reply format")), } } } @@ -195,6 +194,7 @@ where } pub mod type_methods { + use super::*; use std::{ffi::CString, ptr::null_mut}; @@ -218,7 +218,7 @@ pub mod type_methods { pub fn value_rdb_load_json( rdb: *mut raw::RedisModuleIO, encver: c_int, - ) -> Result { + ) -> RedisResult { Ok(match encver { 0 => { let v = backward::json_rdb_load(rdb)?; From b2a150572c08c4151b954c4d22f0046af83a5ff3 Mon Sep 17 00:00:00 2001 From: Ephraim Feldblum Date: Mon, 26 Aug 2024 16:02:32 +0300 Subject: [PATCH 33/33] fmt --- redis_json/src/error.rs | 2 +- redis_json/src/redisjson.rs | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/redis_json/src/error.rs b/redis_json/src/error.rs index b308f2349..5c10c08c5 100644 --- a/redis_json/src/error.rs +++ b/redis_json/src/error.rs @@ -50,7 +50,7 @@ impl From<&str> for Error { Self { msg: e.to_string() } } } - + impl From for Error { fn from(e: serde_json::Error) -> Self { Self { msg: e.to_string() } diff --git a/redis_json/src/redisjson.rs b/redis_json/src/redisjson.rs index 9c649bf0e..27fb016df 100644 --- a/redis_json/src/redisjson.rs +++ b/redis_json/src/redisjson.rs @@ -10,7 +10,7 @@ // User-provided JSON is converted to a tree. This tree is stored transparently in Redis. // It can be operated on (e.g. INCR) and serialized back to JSON. -use redis_module::{raw, RedisResult, RedisError}; +use redis_module::{raw, RedisError, RedisResult}; use std::os::raw::{c_int, c_void}; @@ -215,10 +215,7 @@ pub mod type_methods { } #[allow(non_snake_case, unused)] - pub fn value_rdb_load_json( - rdb: *mut raw::RedisModuleIO, - encver: c_int, - ) -> RedisResult { + pub fn value_rdb_load_json(rdb: *mut raw::RedisModuleIO, encver: c_int) -> RedisResult { Ok(match encver { 0 => { let v = backward::json_rdb_load(rdb)?;