diff --git a/src/error.rs b/src/error.rs index dc8e5e7..0cb6c7c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -83,6 +83,8 @@ pub enum Error { InvalidJsonPathPredicate, InvalidKeyPath, + InvalidJsonType, + Syntax(ParseErrorCode, usize), } diff --git a/src/functions.rs b/src/functions.rs index 08442df..1ee0d42 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -2015,6 +2015,104 @@ fn concat_jsonb(left: &[u8], right: &[u8], buf: &mut Vec) -> Result<(), Erro Ok(()) } +/// Deletes a key (and its value) from a JSON object, or matching string value(s) from a JSON array. +pub fn delete_by_name(value: &[u8], name: &str, buf: &mut Vec) -> Result<(), Error> { + if !is_jsonb(value) { + let mut val = parse_value(value)?; + match &mut val { + Value::Array(arr) => { + arr.retain(|item| !matches!(item, Value::String(v) if v.eq(name))); + } + Value::Object(obj) => { + obj.remove(name); + } + _ => return Err(Error::InvalidJsonType), + }; + val.write_to_vec(buf); + return Ok(()); + } + delete_jsonb_by_name(value, name, buf) +} + +fn delete_jsonb_by_name(value: &[u8], name: &str, buf: &mut Vec) -> Result<(), Error> { + let header = read_u32(value, 0)?; + + match header & CONTAINER_HEADER_TYPE_MASK { + OBJECT_CONTAINER_TAG => { + let mut builder = ObjectBuilder::new(); + for (key, jentry, item) in iterate_object_entries(value, header) { + if !key.eq(name) { + builder.push_raw(key, jentry, item); + } + } + builder.build_into(buf); + } + ARRAY_CONTAINER_TAG => { + let mut builder = ArrayBuilder::new((header & CONTAINER_HEADER_LEN_MASK) as usize); + for (jentry, item) in iterate_array(value, header) { + let matches = match jentry.type_code { + STRING_TAG => { + let v = unsafe { from_utf8_unchecked(item) }; + v.eq(name) + } + _ => false, + }; + if !matches { + builder.push_raw(jentry, item); + } + } + builder.build_into(buf); + } + _ => return Err(Error::InvalidJsonType), + } + Ok(()) +} + +/// Deletes the array element with specified index (negative integers count from the end). +pub fn delete_by_index(value: &[u8], index: i32, buf: &mut Vec) -> Result<(), Error> { + if !is_jsonb(value) { + let mut val = parse_value(value)?; + match &mut val { + Value::Array(arr) => { + let len = arr.len() as i32; + let index = if index < 0 { len - index.abs() } else { index }; + if index >= 0 && index < len { + arr.remove(index as usize); + } + } + _ => return Err(Error::InvalidJsonType), + }; + val.write_to_vec(buf); + return Ok(()); + } + delete_jsonb_by_index(value, index, buf) +} + +fn delete_jsonb_by_index(value: &[u8], index: i32, buf: &mut Vec) -> Result<(), Error> { + let header = read_u32(value, 0)?; + + match header & CONTAINER_HEADER_TYPE_MASK { + ARRAY_CONTAINER_TAG => { + let len = (header & CONTAINER_HEADER_LEN_MASK) as i32; + let index = if index < 0 { len - index.abs() } else { index }; + if index < 0 || index >= len { + buf.extend_from_slice(value); + } else { + let mut builder = ArrayBuilder::new((header & CONTAINER_HEADER_LEN_MASK) as usize); + let index = index as usize; + for (i, entry) in iterate_array(value, header).enumerate() { + if i != index { + builder.push_raw(entry.0, entry.1); + } + } + builder.build_into(buf); + } + } + _ => return Err(Error::InvalidJsonType), + } + Ok(()) +} + /// Deletes all object fields that have null values from the given JSON value, recursively. /// Null values that are not object fields are untouched. pub fn strip_nulls(value: &[u8], buf: &mut Vec) -> Result<(), Error> { diff --git a/src/jsonpath/selector.rs b/src/jsonpath/selector.rs index de9be24..5eef4c8 100644 --- a/src/jsonpath/selector.rs +++ b/src/jsonpath/selector.rs @@ -463,7 +463,7 @@ impl<'a> Selector<'a> { Expr::Paths(paths) => { // get value from path and convert to `ExprValue`. let mut poses = VecDeque::new(); - if let Some(Path::Current) = paths.get(0) { + if let Some(Path::Current) = paths.first() { poses.push_back(pos.clone()); } else { poses.push_back(Position::Container((0, root.len()))); diff --git a/src/lib.rs b/src/lib.rs index 4e463a3..6e26b26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,6 +81,7 @@ mod value; pub use de::from_slice; pub use error::Error; +#[allow(unused_imports)] pub use from::*; pub use functions::*; pub use number::Number; diff --git a/tests/it/functions.rs b/tests/it/functions.rs index 539c986..23b3f36 100644 --- a/tests/it/functions.rs +++ b/tests/it/functions.rs @@ -18,11 +18,11 @@ use std::collections::BTreeMap; use jsonb::{ array_length, array_values, as_bool, as_null, as_number, as_str, build_array, build_object, - compare, concat, contains, convert_to_comparable, exists_all_keys, exists_any_keys, from_slice, - get_by_index, get_by_keypath, get_by_name, get_by_path, is_array, is_object, - keypath::parse_key_paths, object_each, object_keys, parse_value, path_exists, path_match, - strip_nulls, to_bool, to_f64, to_i64, to_pretty_string, to_str, to_string, to_u64, - traverse_check_string, type_of, Number, Object, Value, + compare, concat, contains, convert_to_comparable, delete_by_index, delete_by_name, + exists_all_keys, exists_any_keys, from_slice, get_by_index, get_by_keypath, get_by_name, + get_by_path, is_array, is_object, keypath::parse_key_paths, object_each, object_keys, + parse_value, path_exists, path_match, strip_nulls, to_bool, to_f64, to_i64, to_pretty_string, + to_str, to_string, to_u64, traverse_check_string, type_of, Error, Number, Object, Value, }; use jsonb::jsonpath::parse_json_path; @@ -1290,6 +1290,132 @@ fn test_concat() { } } +#[test] +fn test_delete_by_name() { + let sources = vec![ + ("[1,2,3]", "1", "[1,2,3]"), + (r#"["1","2","3"]"#, "0", r#"["1","2","3"]"#), + (r#"["1","2","3"]"#, "1", r#"["2","3"]"#), + ( + r#"["1","2","3",{"a":1,"b":2}]"#, + "1", + r#"["2","3",{"a":1,"b":2}]"#, + ), + (r#"{"a":1,"b":2}"#, "c", r#"{"a":1,"b":2}"#), + (r#"{"a":1,"b":2}"#, "a", r#"{"b":2}"#), + (r#"{"b":2}"#, "b", "{}"), + ]; + for (json, name, result) in sources { + { + let mut buf = Vec::new(); + delete_by_name(json.as_bytes(), name, &mut buf).unwrap(); + + let actual = from_slice(&buf).unwrap(); + let expected = parse_value(result.as_bytes()).unwrap(); + + assert_eq!(actual, expected); + } + { + let json = parse_value(json.as_bytes()).unwrap().to_vec(); + let mut buf = Vec::new(); + + delete_by_name(&json, name, &mut buf).unwrap(); + + let actual = from_slice(&buf).unwrap(); + let expected = parse_value(result.as_bytes()).unwrap(); + + assert_eq!(actual, expected); + } + } +} + +#[test] +fn test_delete_by_name_errors() { + let sources = vec![(r#""asd""#, "asd"), ("true", "true"), ("1", "1")]; + for (json, name) in sources { + { + let mut buf = Vec::new(); + let result = delete_by_name(json.as_bytes(), name, &mut buf); + + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), Error::InvalidJsonType)); + } + { + let json = parse_value(json.as_bytes()).unwrap().to_vec(); + let mut buf = Vec::new(); + + let result = delete_by_name(&json, name, &mut buf); + + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), Error::InvalidJsonType)); + } + } +} + +#[test] +fn test_delete_by_index() { + let sources = vec![ + ("[1,2,3]", 0, "[2,3]"), + ("[1,2,3]", 1, "[1,3]"), + ("[1,2,3]", 2, "[1,2]"), + ("[1,2,3]", -1, "[1,2]"), + ("[1,2,3]", -2, "[1,3]"), + ("[1,2,3]", -3, "[2,3]"), + ("[1,2,3]", -4, "[1,2,3]"), + (r#"[1,2,{"a":[1,2,3],"b":[40,50,60]}]"#, 2, "[1,2]"), + ]; + for (json, index, result) in sources { + { + let mut buf = Vec::new(); + delete_by_index(json.as_bytes(), index, &mut buf).unwrap(); + + let actual = from_slice(&buf).unwrap(); + let expected = parse_value(result.as_bytes()).unwrap(); + + assert_eq!(actual, expected); + } + { + let json = parse_value(json.as_bytes()).unwrap().to_vec(); + let mut buf = Vec::new(); + + delete_by_index(&json, index, &mut buf).unwrap(); + + let actual = from_slice(&buf).unwrap(); + let expected = parse_value(result.as_bytes()).unwrap(); + + assert_eq!(actual, expected); + } + } +} + +#[test] +fn test_delete_by_index_errors() { + let sources = vec![ + (r#""asd""#, 1), + ("true", 0), + ("1", 10), + (r#"{"a":1,"b":2}"#, 20), + ]; + for (json, index) in sources { + { + let mut buf = Vec::new(); + let result = delete_by_index(json.as_bytes(), index, &mut buf); + + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), Error::InvalidJsonType)); + } + { + let json = parse_value(json.as_bytes()).unwrap().to_vec(); + let mut buf = Vec::new(); + + let result = delete_by_index(&json, index, &mut buf); + + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), Error::InvalidJsonType)); + } + } +} + fn init_object<'a>(entries: Vec<(&str, Value<'a>)>) -> Value<'a> { let mut map = BTreeMap::new(); for (key, val) in entries {