diff --git a/core/function.rs b/core/function.rs index eba490a1f..0bf510e50 100644 --- a/core/function.rs +++ b/core/function.rs @@ -89,6 +89,8 @@ pub enum JsonFunc { JsonbRemove, JsonReplace, JsonbReplace, + JsonInsert, + JsonbInsert, JsonPretty, JsonSet, JsonQuote, @@ -120,6 +122,8 @@ impl Display for JsonFunc { Self::JsonbRemove => "jsonb_remove".to_string(), Self::JsonReplace => "json_replace".to_string(), Self::JsonbReplace => "jsonb_replace".to_string(), + Self::JsonInsert => "json_insert".to_string(), + Self::JsonbInsert => "jsonb_insert".to_string(), Self::JsonPretty => "json_pretty".to_string(), Self::JsonSet => "json_set".to_string(), Self::JsonQuote => "json_quote".to_string(), @@ -593,6 +597,10 @@ impl Func { #[cfg(feature = "json")] "json_replace" => Ok(Self::Json(JsonFunc::JsonReplace)), #[cfg(feature = "json")] + "json_insert" => Ok(Self::Json(JsonFunc::JsonInsert)), + #[cfg(feature = "json")] + "jsonb_insert" => Ok(Self::Json(JsonFunc::JsonbInsert)), + #[cfg(feature = "json")] "jsonb_replace" => Ok(Self::Json(JsonFunc::JsonReplace)), #[cfg(feature = "json")] "json_pretty" => Ok(Self::Json(JsonFunc::JsonPretty)), diff --git a/core/json/json_operations.rs b/core/json/json_operations.rs index 7d48582b2..2d221173c 100644 --- a/core/json/json_operations.rs +++ b/core/json/json_operations.rs @@ -5,7 +5,7 @@ use crate::types::OwnedValue; use super::{ convert_dbtype_to_jsonb, convert_json_to_db_type, get_json_value, json_path_from_owned_value, json_string_to_db_type, - jsonb::{DeleteOperation, ReplaceOperation}, + jsonb::{DeleteOperation, InsertOperation, ReplaceOperation}, Conv, OutputVariant, Val, }; @@ -228,9 +228,53 @@ pub fn jsonb_replace(args: &[OwnedValue]) -> crate::Result { let el_type = json.is_valid()?; + json_string_to_db_type(json, el_type, OutputVariant::AsBinary) +} + +pub fn json_insert(args: &[OwnedValue]) -> crate::Result { + if args.is_empty() { + return Ok(OwnedValue::Null); + } + + let mut json = convert_dbtype_to_jsonb(&args[0], Conv::Strict)?; + let other = args[1..].chunks_exact(2); + for chunk in other { + let path = json_path_from_owned_value(&chunk[0], true)?; + let value = convert_dbtype_to_jsonb(&chunk[1], Conv::NotStrict)?; + if let Some(path) = path { + let mut op = InsertOperation::new(value); + + let _ = json.operate_on_path(&path, &mut op); + } + } + + let el_type = json.is_valid()?; + json_string_to_db_type(json, el_type, OutputVariant::AsString) } +pub fn jsonb_insert(args: &[OwnedValue]) -> crate::Result { + if args.is_empty() { + return Ok(OwnedValue::Null); + } + + let mut json = convert_dbtype_to_jsonb(&args[0], Conv::Strict)?; + let other = args[1..].chunks_exact(2); + for chunk in other { + let path = json_path_from_owned_value(&chunk[0], true)?; + let value = convert_dbtype_to_jsonb(&chunk[1], Conv::NotStrict)?; + if let Some(path) = path { + let mut op = InsertOperation::new(value); + + let _ = json.operate_on_path(&path, &mut op); + } + } + + let el_type = json.is_valid()?; + + json_string_to_db_type(json, el_type, OutputVariant::AsBinary) +} + #[cfg(test)] mod tests { use std::rc::Rc; diff --git a/core/json/mod.rs b/core/json/mod.rs index 3de27ad4f..08555df88 100644 --- a/core/json/mod.rs +++ b/core/json/mod.rs @@ -9,7 +9,7 @@ use crate::bail_constraint_error; pub use crate::json::de::from_str; use crate::json::error::Error as JsonError; pub use crate::json::json_operations::{ - json_patch, json_remove, json_replace, jsonb_remove, jsonb_replace, + json_insert, json_patch, json_remove, json_replace, jsonb_insert, jsonb_remove, jsonb_replace, }; use crate::json::json_path::{json_path, JsonPath, PathElement}; pub use crate::json::ser::to_string; @@ -525,98 +525,6 @@ fn json_path_from_owned_value(path: &OwnedValue, strict: bool) -> crate::Result< Ok(Some(json_path)) } -enum Target<'a> { - Array(&'a mut Vec, usize), - Value(&'a mut Val), -} - -fn create_and_mutate_json_by_path(json: &mut Val, path: JsonPath, closure: F) -> Option -where - F: FnOnce(Target) -> R, -{ - find_or_create_target(json, &path).map(closure) -} - -fn find_or_create_target<'a>(json: &'a mut Val, path: &JsonPath) -> Option> { - let mut current = json; - for (i, key) in path.elements.iter().enumerate() { - let is_last = i == path.elements.len() - 1; - match key { - PathElement::Root() => continue, - PathElement::ArrayLocator(index) => match current { - Val::Array(arr) => { - if let Some(index) = match index { - Some(i) if *i < 0 => arr.len().checked_sub(i.unsigned_abs() as usize), - Some(i) => Some(*i as usize), - None => Some(arr.len()), - } { - if is_last { - if index == arr.len() { - arr.push(Val::Null); - } - - if index >= arr.len() { - return None; - } - - return Some(Target::Array(arr, index)); - } else { - if index == arr.len() { - arr.push( - if matches!(path.elements[i + 1], PathElement::ArrayLocator(_)) - { - Val::Array(vec![]) - } else { - Val::Object(vec![]) - }, - ); - } - - if index >= arr.len() { - return None; - } - - current = &mut arr[index]; - } - } else { - return None; - } - } - _ => { - *current = Val::Array(vec![]); - } - }, - PathElement::Key(key, _) => match current { - Val::Object(obj) => { - if let Some(pos) = &obj - .iter() - .position(|(k, v)| k == key && !matches!(v, Val::Removed)) - { - let val = &mut obj[*pos].1; - current = val; - } else { - let element = if !is_last - && matches!(path.elements[i + 1], PathElement::ArrayLocator(_)) - { - Val::Array(vec![]) - } else { - Val::Object(vec![]) - }; - - obj.push((key.to_string(), element)); - let index = obj.len() - 1; - current = &mut obj[index].1; - } - } - _ => { - return None; - } - }, - } - } - Some(Target::Value(current)) -} - pub fn json_error_position(json: &OwnedValue) -> crate::Result { match json { OwnedValue::Text(t) => match from_str::(t.as_str()) { diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 3b6d9f404..6fbed6b91 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -901,7 +901,9 @@ pub fn translate_expr( | JsonFunc::JsonbExtract | JsonFunc::JsonReplace | JsonFunc::JsonbReplace - | JsonFunc::JsonbRemove => translate_function( + | JsonFunc::JsonbRemove + | JsonFunc::JsonInsert + | JsonFunc::JsonbInsert => translate_function( program, args.as_deref().unwrap_or_default(), referenced_tables, diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 5141c75b8..d46751090 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -52,10 +52,10 @@ use crate::{bail_constraint_error, info, CheckpointStatus}; use crate::{ function::JsonFunc, json::get_json, json::is_json_valid, json::json_array, json::json_array_length, json::json_arrow_extract, json::json_arrow_shift_extract, - json::json_error_position, json::json_extract, json::json_object, json::json_patch, - json::json_quote, json::json_remove, json::json_replace, json::json_set, json::json_type, - json::jsonb, json::jsonb_array, json::jsonb_extract, json::jsonb_object, json::jsonb_remove, - json::jsonb_replace, + json::json_error_position, json::json_extract, json::json_insert, json::json_object, + json::json_patch, json::json_quote, json::json_remove, json::json_replace, json::json_set, + json::json_type, json::jsonb, json::jsonb_array, json::jsonb_extract, json::jsonb_insert, + json::jsonb_object, json::jsonb_remove, json::jsonb_replace, }; use crate::{ resolve_ext_path, Connection, MvCursor, MvStore, Result, TransactionState, DATABASE_VERSION, @@ -2288,6 +2288,24 @@ impl Program { state.registers[*dest] = OwnedValue::Null; } } + JsonFunc::JsonInsert => { + if let Ok(json) = json_insert( + &state.registers[*start_reg..*start_reg + arg_count], + ) { + state.registers[*dest] = json; + } else { + state.registers[*dest] = OwnedValue::Null; + } + } + JsonFunc::JsonbInsert => { + if let Ok(json) = jsonb_insert( + &state.registers[*start_reg..*start_reg + arg_count], + ) { + state.registers[*dest] = json; + } else { + state.registers[*dest] = OwnedValue::Null; + } + } JsonFunc::JsonPretty => { let json_value = &state.registers[*start_reg]; let indent = if arg_count > 1 {