Skip to content

Commit b929b6b

Browse files
gkorlandoshadmi
andauthored
Add JSON.MERGE (RedisJSON#916)
Add support for JSON.MERGE --------- Co-authored-by: Omer Shadmi <76992134+oshadmi@users.noreply.github.com>
1 parent 1296dca commit b929b6b

File tree

9 files changed

+290
-29
lines changed

9 files changed

+290
-29
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ libc = "0.2"
3131
redis-module = { version="1.0", features = ["experimental-api"]}
3232
itertools = "0.10"
3333
regex = "1"
34+
3435
[features]
3536
# Workaround to allow cfg(feature = "test") in redismodue-rs dependencies:
3637
# https://github.com/RedisLabsModules/redismodule-rs/pull/68

commands.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,26 @@
139139
"since": "1.0.0",
140140
"group": "json"
141141
},
142+
"JSON.MERGE": {
143+
"summary": "Sets or updates the JSON value at a path",
144+
"complexity": "O(M+N) when path is evaluated to a single value where M is the size of the original value (if it exists) and N is the size of the new value, O(M+N) when path is evaluated to multiple values where M is the size of the key and N is the size of the new value",
145+
"arguments": [
146+
{
147+
"name": "key",
148+
"type": "key"
149+
},
150+
{
151+
"name": "path",
152+
"type": "string"
153+
},
154+
{
155+
"name": "value",
156+
"type": "string"
157+
}
158+
],
159+
"since": "2.6.0",
160+
"group": "json"
161+
},
142162
"JSON.MGET": {
143163
"summary": "Returns the values at a path from one or more keys",
144164
"complexity": "O(M*N) when path is evaluated to a single value where M is the number of keys and N is the size of the value, O(N1+N2+...+Nm) when path is evaluated to multiple values where m is the number of keys and Ni is the size of the i-th key",

src/commands.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,86 @@ pub fn json_set<M: Manager>(manager: M, ctx: &Context, args: Vec<RedisString>) -
201201
}
202202
}
203203

204+
///
205+
/// JSON.MERGE <key> <path> <json> [FORMAT <format>]
206+
///
207+
pub fn json_merge<M: Manager>(manager: M, ctx: &Context, args: Vec<RedisString>) -> RedisResult {
208+
let mut args = args.into_iter().skip(1);
209+
210+
let key = args.next_arg()?;
211+
let path = Path::new(args.next_str()?);
212+
let value = args.next_str()?;
213+
214+
let mut format = Format::JSON;
215+
216+
while let Some(s) = args.next() {
217+
match s.try_as_str()? {
218+
arg if arg.eq_ignore_ascii_case("FORMAT") => {
219+
format = Format::from_str(args.next_str()?)?;
220+
}
221+
_ => return Err(RedisError::Str("ERR syntax error")),
222+
};
223+
}
224+
225+
let mut redis_key = manager.open_key_write(ctx, key)?;
226+
let current = redis_key.get_value()?;
227+
228+
let val = manager.from_str(value, format, true)?;
229+
230+
match current {
231+
Some(doc) => {
232+
if path.get_path() == JSON_ROOT_PATH {
233+
redis_key.merge_value(Vec::new(), val)?;
234+
redis_key.apply_changes(ctx, "json.merge")?;
235+
REDIS_OK
236+
} else {
237+
let mut update_info =
238+
KeyValue::new(doc).find_paths(path.get_path(), &SetOptions::None)?;
239+
if !update_info.is_empty() {
240+
let mut res = false;
241+
if update_info.len() == 1 {
242+
res = match update_info.pop().unwrap() {
243+
UpdateInfo::SUI(sui) => redis_key.merge_value(sui.path, val)?,
244+
UpdateInfo::AUI(aui) => redis_key.dict_add(aui.path, &aui.key, val)?,
245+
}
246+
} else {
247+
for ui in update_info {
248+
res = match ui {
249+
UpdateInfo::SUI(sui) => {
250+
redis_key.merge_value(sui.path, val.clone())?
251+
}
252+
UpdateInfo::AUI(aui) => {
253+
redis_key.dict_add(aui.path, &aui.key, val.clone())?
254+
}
255+
} || res; // If any of the updates succeed, return true
256+
}
257+
}
258+
if res {
259+
redis_key.apply_changes(ctx, "json.merge")?;
260+
REDIS_OK
261+
} else {
262+
Ok(RedisValue::Null)
263+
}
264+
} else {
265+
Ok(RedisValue::Null)
266+
}
267+
}
268+
}
269+
None => {
270+
if path.get_path() == JSON_ROOT_PATH {
271+
// Nothing to merge with it's a new doc
272+
redis_key.set_value(Vec::new(), val)?;
273+
redis_key.apply_changes(ctx, "json.merge")?;
274+
REDIS_OK
275+
} else {
276+
Err(RedisError::Str(
277+
"ERR new objects must be created at the root",
278+
))
279+
}
280+
}
281+
}
282+
}
283+
204284
///
205285
/// JSON.MSET <key> <path> <json> [[<key> <path> <json>]...]
206286
///

src/ivalue_manager.rs

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::redisjson::normalize_arr_start_index;
1111
use crate::Format;
1212
use crate::REDIS_JSON_TYPE;
1313
use ijson::object::Entry;
14-
use ijson::{DestructuredMut, INumber, IString, IValue, ValueType};
14+
use ijson::{DestructuredMut, INumber, IObject, IString, IValue, ValueType};
1515
use redis_module::key::{verify_type, RedisKey, RedisKeyWritable};
1616
use redis_module::raw::{RedisModuleKey, Status};
1717
use redis_module::rediserror::RedisError;
@@ -54,43 +54,31 @@ fn replace<F: FnMut(&mut IValue) -> Result<Option<IValue>, Error>>(
5454
let target_once = target;
5555
let is_last = i == last_index;
5656
let target_opt = match target_once.type_() {
57-
// ValueType::Object(ref mut map) => {
5857
ValueType::Object => {
5958
let obj = target_once.as_object_mut().unwrap();
6059
if is_last {
6160
if let Entry::Occupied(mut e) = obj.entry(token) {
6261
let v = e.get_mut();
63-
match (func)(v) {
64-
Ok(res) => {
65-
if let Some(res) = res {
66-
*v = res;
67-
} else {
68-
e.remove();
69-
}
70-
}
71-
Err(err) => return Err(err),
62+
if let Some(res) = (func)(v)? {
63+
*v = res;
64+
} else {
65+
e.remove();
7266
}
7367
}
7468
return Ok(());
7569
}
7670
obj.get_mut(token.as_str())
7771
}
78-
// Value::Array(ref mut vec) => {
7972
ValueType::Array => {
8073
let arr = target_once.as_array_mut().unwrap();
8174
if let Ok(x) = token.parse::<usize>() {
8275
if is_last {
8376
if x < arr.len() {
8477
let v = &mut arr.as_mut_slice()[x];
85-
match (func)(v) {
86-
Ok(res) => {
87-
if let Some(res) = res {
88-
*v = res;
89-
} else {
90-
arr.remove(x);
91-
}
92-
}
93-
Err(err) => return Err(err),
78+
if let Some(res) = (func)(v)? {
79+
*v = res;
80+
} else {
81+
arr.remove(x);
9482
}
9583
}
9684
return Ok(());
@@ -330,14 +318,30 @@ impl<'a> WriteHolder<IValue, IValue> for IValueKeyHolderWrite<'a> {
330318
self.set_root(Some(v))?;
331319
updated = true;
332320
} else {
333-
replace(&path, self.get_value().unwrap().unwrap(), |_v| {
321+
replace(&path, self.get_value()?.unwrap(), |_v| {
334322
updated = true;
335323
Ok(Some(v.take()))
336324
})?;
337325
}
338326
Ok(updated)
339327
}
340328

329+
fn merge_value(&mut self, path: Vec<String>, v: IValue) -> Result<bool, RedisError> {
330+
let mut updated = false;
331+
if path.is_empty() {
332+
merge(self.get_value()?.unwrap(), &v);
333+
// update the root
334+
updated = true;
335+
} else {
336+
replace(&path, self.get_value()?.unwrap(), |current| {
337+
updated = true;
338+
merge(current, &v);
339+
Ok(Some(current.take()))
340+
})?;
341+
}
342+
Ok(updated)
343+
}
344+
341345
fn dict_add(
342346
&mut self,
343347
path: Vec<String>,
@@ -551,6 +555,25 @@ impl ReadHolder<IValue> for IValueKeyHolderRead {
551555
}
552556
}
553557

558+
fn merge(doc: &mut IValue, patch: &IValue) {
559+
if !patch.is_object() {
560+
*doc = patch.clone();
561+
return;
562+
}
563+
564+
if !doc.is_object() {
565+
*doc = IObject::new().into();
566+
}
567+
let map = doc.as_object_mut().unwrap();
568+
for (key, value) in patch.as_object().unwrap() {
569+
if value.is_null() {
570+
map.remove(key.as_str());
571+
} else {
572+
merge(map.entry(key.as_str()).or_insert(IValue::NULL), value);
573+
}
574+
}
575+
}
576+
554577
pub struct RedisIValueJsonKeyManager<'a> {
555578
pub phantom: PhantomData<&'a u64>,
556579
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ macro_rules! redis_json_module_create {(
229229
["json.debug", json_command!(json_debug), "readonly", 2,2,1],
230230
["json.forget", json_command!(json_del), "write", 1,1,1],
231231
["json.resp", json_command!(json_resp), "readonly", 1,1,1],
232+
["json.merge", json_command!(json_merge), "write deny-oom", 1,1,1],
232233
],
233234
}
234235
}

src/manager.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub trait WriteHolder<O: Clone, V: SelectValue> {
3939
fn delete(&mut self) -> Result<(), RedisError>;
4040
fn get_value(&mut self) -> Result<Option<&mut V>, RedisError>;
4141
fn set_value(&mut self, path: Vec<String>, v: O) -> Result<bool, RedisError>;
42+
fn merge_value(&mut self, path: Vec<String>, v: O) -> Result<bool, RedisError>;
4243
fn dict_add(&mut self, path: Vec<String>, key: &str, v: O) -> Result<bool, RedisError>;
4344
fn delete_path(&mut self, path: Vec<String>) -> Result<bool, RedisError>;
4445
fn incr_by(&mut self, path: Vec<String>, num: &str) -> Result<Number, RedisError>;

src/serde_value_manager.rs

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub struct KeyHolderWrite<'a> {
3636
val: Option<&'a mut RedisJSON<Value>>,
3737
}
3838

39-
fn update<F: FnMut(Value) -> Result<Option<Value>, Error>>(
39+
fn replace<F: FnMut(Value) -> Result<Option<Value>, Error>>(
4040
path: &[String],
4141
root: &mut Value,
4242
mut func: F,
@@ -112,7 +112,7 @@ impl<'a> KeyHolderWrite<'a> {
112112
}
113113
}
114114
} else {
115-
update(paths, self.get_value().unwrap().unwrap(), op_fun)?;
115+
replace(paths, self.get_value().unwrap().unwrap(), op_fun)?;
116116
}
117117

118118
Ok(())
@@ -223,14 +223,30 @@ impl<'a> WriteHolder<Value, Value> for KeyHolderWrite<'a> {
223223
self.set_root(Some(v))?;
224224
updated = true;
225225
} else {
226-
update(&path, self.get_value().unwrap().unwrap(), |_v| {
226+
replace(&path, self.get_value()?.unwrap(), |_v| {
227227
updated = true;
228228
Ok(Some(v.take()))
229229
})?;
230230
}
231231
Ok(updated)
232232
}
233233

234+
fn merge_value(&mut self, path: Vec<String>, v: Value) -> Result<bool, RedisError> {
235+
let mut updated = false;
236+
if path.is_empty() {
237+
let val = merge(self.get_value()?.unwrap(), &v);
238+
self.set_root(Some(val))?;
239+
// update the root
240+
updated = true;
241+
} else {
242+
replace(&path, self.get_value()?.unwrap(), |mut current| {
243+
updated = true;
244+
Ok(Some(merge(&mut current, &v)))
245+
})?;
246+
}
247+
Ok(updated)
248+
}
249+
234250
fn dict_add(&mut self, path: Vec<String>, key: &str, mut v: Value) -> Result<bool, RedisError> {
235251
let mut updated = false;
236252
if path.is_empty() {
@@ -247,7 +263,7 @@ impl<'a> WriteHolder<Value, Value> for KeyHolderWrite<'a> {
247263
};
248264
self.set_root(Some(val))?;
249265
} else {
250-
update(&path, self.get_value().unwrap().unwrap(), |val| {
266+
replace(&path, self.get_value().unwrap().unwrap(), |val| {
251267
let val = if let Value::Object(mut o) = val {
252268
if !o.contains_key(key) {
253269
updated = true;
@@ -265,7 +281,7 @@ impl<'a> WriteHolder<Value, Value> for KeyHolderWrite<'a> {
265281

266282
fn delete_path(&mut self, path: Vec<String>) -> Result<bool, RedisError> {
267283
let mut deleted = false;
268-
update(&path, self.get_value().unwrap().unwrap(), |_v| {
284+
replace(&path, self.get_value().unwrap().unwrap(), |_v| {
269285
deleted = true; // might delete more than a single value
270286
Ok(None)
271287
})?;
@@ -445,6 +461,30 @@ impl ReadHolder<Value> for KeyHolderRead {
445461
}
446462
}
447463

464+
fn merge(doc: &mut Value, patch: &Value) -> Value {
465+
if !patch.is_object() || !doc.is_object() {
466+
return patch.clone();
467+
}
468+
469+
let mut res = doc.take();
470+
if let Value::Object(ref mut map) = res {
471+
for (key, value) in patch.as_object().unwrap() {
472+
if value.is_null() {
473+
map.remove(key.as_str());
474+
} else {
475+
let curr = map.entry(key.as_str());
476+
if let Entry::Occupied(mut val) = curr {
477+
let new_val = merge(val.get_mut(), value);
478+
val.insert(new_val);
479+
} else {
480+
map.insert(key.clone(), value.clone());
481+
}
482+
}
483+
}
484+
}
485+
res
486+
}
487+
448488
pub struct RedisJsonKeyManager<'a> {
449489
pub phantom: PhantomData<&'a u64>,
450490
}

0 commit comments

Comments
 (0)