diff --git a/Cargo.lock b/Cargo.lock index 911ebb131..09d915c42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -541,7 +541,7 @@ dependencies = [ [[package]] name = "redisjson" -version = "2.0.3" +version = "2.0.4" dependencies = [ "bson", "itertools", diff --git a/Cargo.toml b/Cargo.toml index 497e65735..9e98f62d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "redisjson" -version = "2.0.3" +version = "2.0.4" authors = ["Gavrie Philipson "] edition = "2018" diff --git a/README.md b/README.md index ff5294b7c..093fcdf9a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ [![GitHub issues](https://img.shields.io/github/release/RedisJSON/RedisJSON.svg)](https://github.com/RedisJSON/RedisJSON/releases/latest) [![CircleCI](https://circleci.com/gh/RedisJSON/RedisJSON/tree/master.svg?style=svg)](https://circleci.com/gh/RedisJSON/RedisJSON/tree/master) [![macos](https://github.com/RedisJSON/RedisJSON/workflows/macos/badge.svg)](https://github.com/RedisJSON/RedisJSON/actions?query=workflow%3Amacos) -[![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/redislabs/rejson.svg)](https://hub.docker.com/r/redislabs/rejson/builds/) +[![Dockerhub](https://img.shields.io/badge/dockerhub-redislabs%2Frejson-blue)](https://hub.docker.com/r/redislabs/rejson/tags/) [![Total alerts](https://img.shields.io/lgtm/alerts/g/RedisJSON/RedisJSON.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/RedisJSON/RedisJSON/alerts/) + +# RedisJSON [![Forum](https://img.shields.io/badge/Forum-RedisJSON-blue)](https://forum.redislabs.com/c/modules/redisjson) [![Discord](https://img.shields.io/discord/697882427875393627?style=flat-square)](https://discord.gg/QUkjSsk) -# RedisJSON RedisJSON is a [Redis](https://redis.io/) module that implements [ECMA-404 The JSON Data Interchange Standard](https://json.org/) as a native data type. It allows storing, updating and fetching JSON values from Redis keys (documents). diff --git a/commands.json b/commands.json index 45f6ae4f0..79364a794 100644 --- a/commands.json +++ b/commands.json @@ -9,7 +9,7 @@ }, { "name": "path", - "type": "json path string", + "type": "string", "optional": true } ], @@ -49,11 +49,58 @@ }, { "name": "paths", - "type": "json path string", + "type": "string", + "optional": true + } + ], + "since": "1.0.0", + "group": "json" + }, + "JSON.SET": { + "summary": "Sets the JSON value at path in key", + "complexity": "O(M+N), where M is the size of the original value (if it exists) and N is the size of the new value", + "arguments": [ + { + "name": "key", + "type": "key" + }, + { + "name": "path", + "type": "string" + }, + { + "name": "json", + "type": "string" + }, + { + "name": "condition", + "type": "enum", + "enum": [ + "NX", + "XX" + ], + "optional": true + } + ], + "since": "1.0.0", + "group": "json" + }, + "JSON.OBJLEN": { + "summary": "Report the number of keys in the JSON Object at path in key", + "complexity": "O(1)", + "arguments": [ + { + "name": "key", + "type": "key" + }, + { + "name": "path", + "type": "string", "optional": true } ], "since": "1.0.0", "group": "json" } -} \ No newline at end of file + +} diff --git a/src/commands.rs b/src/commands.rs index 01a715430..c2ebc7bc9 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -16,7 +16,7 @@ use crate::redisjson::SetOptions; use serde_json::{Number, Value}; use itertools::FoldWhile::{Continue, Done}; -use itertools::Itertools; +use itertools::{EitherOrBoth, Itertools}; use serde::{Serialize, Serializer}; use std::collections::HashMap; @@ -758,40 +758,43 @@ where .collect::>() } -/// Sort the paths so higher indices precede lower indices on the same on the same array -/// And objects with higher hierarchy (closer to the top-level) preceded objects with deeper hierarchy +/// Sort the paths so higher indices precede lower indices on the same array, +/// And if one path is a sub-path of the other, then paths with shallower hierarchy (closer to the top-level) precedes paths with deeper hierarchy fn prepare_paths_for_deletion(paths: &mut Vec>) { - paths.sort_by(|v1, v2| match (v1.len(), v2.len()) { - (l1, l2) if l1 < l2 => Ordering::Less, // Shorter paths before longer paths - (l1, l2) if l1 > l2 => Ordering::Greater, // Shorter paths before longer paths - _ => v1 - .iter() - .zip(v2.iter()) - .fold_while(Ordering::Equal, |_acc, (p1, p2)| { - let i1 = p1.parse::(); - let i2 = p2.parse::(); - match (i1, i2) { - (Err(_), Err(_)) => match p1.cmp(p2) { - // String compare - Ordering::Less => Done(Ordering::Less), - Ordering::Equal => Continue(Ordering::Equal), - Ordering::Greater => Done(Ordering::Greater), - }, - (Ok(_), Err(_)) => Done(Ordering::Greater), //String before Numeric - (Err(_), Ok(_)) => Done(Ordering::Less), //String before Numeric - (Ok(i1), Ok(i2)) => { - // Numeric compare - higher indices before lower ones - if i1 < i2 { - Done(Ordering::Greater) - } else if i2 < i1 { - Done(Ordering::Less) - } else { - Continue(Ordering::Equal) + paths.sort_by(|v1, v2| { + v1.iter() + .zip_longest(v2.iter()) + .fold_while(Ordering::Equal, |_acc, v| { + match v { + EitherOrBoth::Left(_) => Done(Ordering::Greater), // Shorter paths before longer paths + EitherOrBoth::Right(_) => Done(Ordering::Less), // Shorter paths before longer paths + EitherOrBoth::Both(p1, p2) => { + let i1 = p1.parse::(); + let i2 = p2.parse::(); + match (i1, i2) { + (Err(_), Err(_)) => match p1.cmp(p2) { + // String compare + Ordering::Less => Done(Ordering::Less), + Ordering::Equal => Continue(Ordering::Equal), + Ordering::Greater => Done(Ordering::Greater), + }, + (Ok(_), Err(_)) => Done(Ordering::Greater), //String before Numeric + (Err(_), Ok(_)) => Done(Ordering::Less), //String before Numeric + (Ok(i1), Ok(i2)) => { + // Numeric compare - higher indices before lower ones + if i1 < i2 { + Done(Ordering::Greater) + } else if i2 < i1 { + Done(Ordering::Less) + } else { + Continue(Ordering::Equal) + } + } } } } }) - .into_inner(), + .into_inner() }); } diff --git a/src/lib.rs b/src/lib.rs index 8e9b3a2ea..de32d51ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -463,6 +463,6 @@ redis_json_module_create! { data_types: [REDIS_JSON_TYPE], pre_command_function: pre_command, get_manage: Some(manager::RedisJsonKeyManager{phantom:PhantomData}), - version: 02_00_03, + version: 02_00_04, init: dummy_init, } diff --git a/tests/pytest/test_multi.py b/tests/pytest/test_multi.py index c475b2301..21db6a7c0 100644 --- a/tests/pytest/test_multi.py +++ b/tests/pytest/test_multi.py @@ -69,6 +69,10 @@ def testDelCommand(env): res = r.execute_command('JSON.DEL', 'doc2', '$[2,1,0]') r.assertEqual(res, 3) + r.assertOk(r.execute_command('JSON.SET', 'doc2', '$', '[1, 2, 3]')) + res = r.execute_command('JSON.DEL', 'doc2', '$[1,2,0]') + r.assertEqual(res, 3) + r.assertOk(r.execute_command('JSON.SET', 'doc2', '$', '{"b": [1,2,3], "a": {"b": [1, 2, 3], "c": [1, 2, 3]}, "x": {"b": [1, 2, 3], "c": [1, 2, 3]}}')) res = r.execute_command('JSON.DEL', 'doc2', '$..x.b[*]') r.assertEqual(res, 3) @@ -88,6 +92,14 @@ def testDelCommand(env): res = r.execute_command('JSON.GET', 'doc2', '$') r.assertEqual(json.loads(res), [[True, {"answer": 42}]]) +def testDelCommand_issue529(env): + r = env + r.assertOk(r.execute_command('JSON.SET', 'doc1', '$', '[{"a00": [{"a00": "a00_00"}, {"a01": "a00_01"}, {"a02": "a00_02"}, {"a03": "a00_03"}]}, {"a01": [{"a00": "a01_00"}, {"a01": "a01_01"}, {"a02": "a01_02"}, {"a03": "a01_03"}]}, {"a02": [{"a00": "a02_00"}, {"a01": "a02_01"}, {"a02": "a02_02"}, {"a03": "a02_03"}]}, {"a03": [{"a00": "a03_00"}, {"a01": "a03_01"}, {"a02": "a03_02"}, {"a03": "a03_03"}]}]')) + res = r.execute_command('JSON.DEL', 'doc1', '$..[2]') + r.assertEqual(res, 4) + res = r.execute_command('JSON.ARRLEN', 'doc1', '$.*[*]') + r.assertEqual(res, [3, 3, 3]) + def testForgetCommand(env): """Test REJSON.FORGET command"""