Skip to content
This repository was archived by the owner on Apr 20, 2020. It is now read-only.

Commit 89b8a95

Browse files
authored
Add BSON format, JSON.DEBUG & Remove submodule, (#37)
* get_doc: return error on wrong path * redisjson.rs: turn num_op into a generic value_op * lib.rs: add support for STRAPPEND, ARRAPPEND, ARRINSERT, ARRPOP * Add support for JSON.ARRINDEX * Add support for JSON.ARRTRIM & fix ARRINSERT to accept negative index * Add support for JSON.OBJKEYS * add special handling for path=$ (root), can't call replace_with * fix returned index in case start>0 * Return poped value on arrpop and fix error handle on replace with wrong path * fix path backward on ARRPOP * fix ARRPOP for index<0 * Fix commands for backward: * STRAPPEND & ARRAPPNED return the len after update * MGET returns nil when path not found for a key * uncomment more tests * remove sub-module (set redismodule-rs repo in Cargo.toml) * inital BSON support * add debug command (#38)
1 parent babe11e commit 89b8a95

File tree

9 files changed

+396
-55
lines changed

9 files changed

+396
-55
lines changed

Cargo.lock

Lines changed: 200 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ edition = "2018"
88
crate-type = ["cdylib"]
99

1010
[dependencies]
11+
bson = "0.13.0"
1112
serde_json = "1.0"
1213
libc = "0.2"
1314
jsonpath_lib = { git="https://github.com/gkorland/jsonpath.git", branch="reaplce_with_ownership_parser" }

redismodule-rs

Submodule redismodule-rs deleted from 5c41d48

src/lib.rs

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ mod nodevisitor;
1212
mod redisjson;
1313

1414
use crate::index::Index;
15-
use crate::redisjson::{Error, RedisJSON, SetOptions};
15+
use crate::redisjson::{Error, Format, RedisJSON, SetOptions};
1616

1717
static JSON_TYPE_ENCODING_VERSION: i32 = 2;
1818
static JSON_TYPE_NAME: &str = "ReJSON-RL";
@@ -84,29 +84,37 @@ fn json_set(ctx: &Context, args: Vec<String>) -> RedisResult {
8484
let path = backwards_compat_path(args.next_string()?);
8585
let value = args.next_string()?;
8686

87-
let set_option = args
88-
.next()
89-
.map(|op| match op.to_uppercase().as_str() {
90-
"NX" => Ok(SetOptions::NotExists),
91-
"XX" => Ok(SetOptions::AlreadyExists),
92-
_ => Err(RedisError::Str("ERR syntax error")),
93-
})
94-
.transpose()?;
87+
let mut format = Format::JSON;
88+
let mut set_option = SetOptions::None;
89+
loop {
90+
if let Some(s) = args.next() {
91+
match s.to_uppercase().as_str() {
92+
"NX" => set_option = SetOptions::NotExists,
93+
"XX" => set_option = SetOptions::AlreadyExists,
94+
"FORMAT" => {
95+
format = Format::from_str(args.next_string()?.as_str())?;
96+
}
97+
_ => break,
98+
};
99+
} else {
100+
break;
101+
}
102+
}
95103

96104
let key = ctx.open_key_writable(&key);
97105
let current = key.get_value::<RedisJSON>(&REDIS_JSON_TYPE)?;
98106

99107
match (current, set_option) {
100108
(Some(ref mut doc), ref op) => {
101-
if doc.set_value(&value, &path, op)? {
109+
if doc.set_value(&value, &path, op, format)? {
102110
REDIS_OK
103111
} else {
104112
Ok(RedisValue::None)
105113
}
106114
}
107-
(None, Some(SetOptions::AlreadyExists)) => Ok(RedisValue::None),
115+
(None, SetOptions::AlreadyExists) => Ok(RedisValue::None),
108116
(None, _) => {
109-
let doc = RedisJSON::from_str(&value)?;
117+
let doc = RedisJSON::from_str(&value, format)?;
110118
if path == "$" {
111119
key.set_value(&REDIS_JSON_TYPE, doc)?;
112120
REDIS_OK
@@ -128,29 +136,55 @@ fn json_set(ctx: &Context, args: Vec<String>) -> RedisResult {
128136
/// TODO add support for multi path
129137
fn json_get(ctx: &Context, args: Vec<String>) -> RedisResult {
130138
let mut args = args.into_iter().skip(1);
131-
132139
let key = args.next_string()?;
133140

134-
let mut path = loop {
141+
let mut paths: Vec<String> = vec![];
142+
let mut first_loop = true;
143+
let mut format = Format::JSON;
144+
loop {
135145
let arg = match args.next_string() {
136146
Ok(s) => s,
137-
Err(_) => "$".to_owned(), // path is optional
147+
Err(_) => {
148+
// path is optional -> no path found on the first loop we use root "$"
149+
if first_loop {
150+
paths.push("$".to_owned());
151+
}
152+
break;
153+
}
138154
};
155+
first_loop = false;
139156

140157
match arg.as_str() {
141-
"INDENT" => args.next(), // TODO add support
142-
"NEWLINE" => args.next(), // TODO add support
143-
"SPACE" => args.next(), // TODO add support
144-
"NOESCAPE" => continue, // TODO add support
145-
_ => break arg,
158+
"INDENT" => {
159+
args.next();
160+
} // TODO add support
161+
"NEWLINE" => {
162+
args.next();
163+
} // TODO add support
164+
"SPACE" => {
165+
args.next();
166+
} // TODO add support
167+
"NOESCAPE" => {
168+
continue;
169+
} // TODO add support
170+
"FORMAT" => {
171+
format = Format::from_str(args.next_string()?.as_str())?;
172+
}
173+
_ => {
174+
paths.push(backwards_compat_path(arg));
175+
}
146176
};
147-
};
148-
path = backwards_compat_path(path);
177+
}
149178

150179
let key = ctx.open_key_writable(&key);
151-
152180
let value = match key.get_value::<RedisJSON>(&REDIS_JSON_TYPE)? {
153-
Some(doc) => doc.to_string(&path)?.into(),
181+
Some(doc) => if paths.len() == 1 {
182+
doc.to_string(&paths[0], format)?
183+
} else {
184+
// can't be smaller than 1
185+
doc.to_json(&mut paths)?
186+
}
187+
.into(),
154188
None => RedisValue::None,
155189
};
156190

@@ -175,7 +209,7 @@ fn json_mget(ctx: &Context, args: Vec<String>) -> RedisResult {
175209
let result = ctx
176210
.open_key(key)
177211
.get_value::<RedisJSON>(&REDIS_JSON_TYPE)?
178-
.map(|doc| doc.to_string(&path))
212+
.map(|doc| doc.to_string(&path, Format::JSON))
179213
.transpose()?;
180214

181215
Ok(result.into())
@@ -572,8 +606,29 @@ fn json_obj_len(ctx: &Context, args: Vec<String>) -> RedisResult {
572606
/// MEMORY <key> [path]
573607
/// HELP
574608
///
575-
fn json_debug(_ctx: &Context, _args: Vec<String>) -> RedisResult {
576-
Err("Command was not implemented".into())
609+
fn json_debug(ctx: &Context, args: Vec<String>) -> RedisResult {
610+
let mut args = args.into_iter().skip(1);
611+
match args.next_string()?.to_uppercase().as_str() {
612+
"MEMORY" => {
613+
let key = args.next_string()?;
614+
let path = backwards_compat_path(args.next_string()?);
615+
616+
let key = ctx.open_key(&key);
617+
let value = match key.get_value::<RedisJSON>(&REDIS_JSON_TYPE)? {
618+
Some(doc) => doc.get_memory(&path)?,
619+
None => 0,
620+
};
621+
Ok(value.into())
622+
}
623+
"HELP" => {
624+
let results = vec![
625+
"MEMORY <key> [path] - reports memory usage",
626+
"HELP - this message",
627+
];
628+
Ok(results.into())
629+
}
630+
_ => Err("ERR unknown subcommand - try `JSON.DEBUG HELP`".into()),
631+
}
577632
}
578633

579634
///

src/redisjson.rs

Lines changed: 94 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
// Translate between JSON and tree of Redis objects:
44
// User-provided JSON is converted to a tree. This tree is stored transparently in Redis.
55
// It can be operated on (e.g. INCR) and serialized back to JSON.
6+
7+
use bson::decode_document;
68
use crate::backward;
79
use crate::nodevisitor::NodeVisitorImpl;
810
use jsonpath_lib::{JsonPathError, SelectorMut};
911
use redismodule::raw;
10-
use serde_json::Value;
12+
use serde_json::{Map, Value};
13+
use std::io::Cursor;
14+
use std::mem;
1115
use std::os::raw::{c_int, c_void};
1216

1317
#[derive(Debug)]
@@ -19,6 +23,7 @@ pub struct Error {
1923
pub enum SetOptions {
2024
NotExists,
2125
AlreadyExists,
26+
None,
2227
}
2328

2429
impl From<String> for Error {
@@ -53,12 +58,50 @@ impl From<Error> for redismodule::RedisError {
5358
}
5459
}
5560

61+
#[derive(Debug, PartialEq)]
62+
pub enum Format {
63+
JSON,
64+
BSON,
65+
}
66+
67+
impl Format {
68+
pub fn from_str(s: &str) -> Result<Format, Error> {
69+
match s {
70+
"JSON" => Ok(Format::JSON),
71+
"BSON" => Ok(Format::BSON),
72+
_ => return Err("ERR wrong format".into()),
73+
}
74+
}
75+
}
76+
5677
#[derive(Debug)]
5778
pub struct RedisJSON {
5879
data: Value,
5980
}
6081

6182
impl RedisJSON {
83+
pub fn parse_str(data: &str, format: Format) -> Result<Value, Error> {
84+
match format {
85+
Format::JSON => Ok(serde_json::from_str(data)?),
86+
Format::BSON => decode_document(&mut Cursor::new(data.as_bytes()))
87+
.map(|docs| {
88+
let v = if docs.len() >= 1 {
89+
docs.iter()
90+
.next()
91+
.map_or_else(|| Value::Null, |(_, b)| b.clone().into())
92+
} else {
93+
Value::Null
94+
};
95+
Ok(v)
96+
})
97+
.unwrap_or_else(|e| Err(e.to_string().into())),
98+
}
99+
}
100+
101+
pub fn from_str(data: &str, format: Format) -> Result<Self, Error> {
102+
let value = RedisJSON::parse_str(data, format)?;
103+
Ok(Self { data: value })
104+
}
62105
fn add_value(&mut self, path: &str, value: Value) -> Result<bool, Error> {
63106
if NodeVisitorImpl::check(path)? {
64107
let mut splits = path.rsplitn(2, '.');
@@ -99,32 +142,24 @@ impl RedisJSON {
99142
}
100143
}
101144

102-
pub fn from_str(data: &str) -> Result<Self, Error> {
103-
// Parse the string of data into serde_json::Value.
104-
let v: Value = serde_json::from_str(data)?;
105-
106-
Ok(Self { data: v })
107-
}
108-
109145
pub fn set_value(
110146
&mut self,
111147
data: &str,
112148
path: &str,
113-
option: &Option<SetOptions>,
149+
option: &SetOptions,
150+
format: Format
114151
) -> Result<bool, Error> {
115-
// Parse the string of data into serde_json::Value.
116-
let json: Value = serde_json::from_str(data)?;
117-
152+
let json: Value = RedisJSON::parse_str(data, format)?;
118153
if path == "$" {
119-
if Some(SetOptions::NotExists) == *option {
154+
if SetOptions::NotExists == *option {
120155
Ok(false)
121156
} else {
122157
self.data = json;
123158
Ok(true)
124159
}
125160
} else {
126161
let mut replaced = false;
127-
if Some(SetOptions::NotExists) != *option {
162+
if SetOptions::NotExists != *option {
128163
let current_data = self.data.take();
129164
self.data = jsonpath_lib::replace_with(current_data, path, &mut |_v| {
130165
replaced = true;
@@ -133,7 +168,7 @@ impl RedisJSON {
133168
}
134169
if replaced {
135170
Ok(true)
136-
} else if Some(SetOptions::AlreadyExists) != *option {
171+
} else if SetOptions::AlreadyExists != *option {
137172
self.add_value(path, json)
138173
} else {
139174
Ok(false)
@@ -154,9 +189,37 @@ impl RedisJSON {
154189
Ok(deleted)
155190
}
156191

157-
pub fn to_string(&self, path: &str) -> Result<String, Error> {
192+
pub fn to_string(&self, path: &str, format: Format) -> Result<String, Error> {
158193
let results = self.get_doc(path)?;
159-
Ok(serde_json::to_string(&results)?)
194+
let res = match format {
195+
Format::JSON => serde_json::to_string(&results)?,
196+
Format::BSON => return Err("Soon to come...".into()) //results.into() as Bson,
197+
};
198+
Ok(res)
199+
}
200+
201+
pub fn to_json(&self, paths: &mut Vec<String>) -> Result<String, Error> {
202+
let mut selector = jsonpath_lib::selector(&self.data);
203+
let mut result = paths.drain(..).fold(String::from("{"), |mut acc, path| {
204+
let value = match selector(&path) {
205+
Ok(s) => match s.first() {
206+
Some(v) => v,
207+
None => &Value::Null,
208+
},
209+
Err(_) => &Value::Null,
210+
};
211+
acc.push('\"');
212+
acc.push_str(&path);
213+
acc.push_str("\":");
214+
acc.push_str(value.to_string().as_str());
215+
acc.push(',');
216+
acc
217+
});
218+
if result.ends_with(",") {
219+
result.pop();
220+
}
221+
result.push('}');
222+
Ok(result.into())
160223
}
161224

162225
pub fn str_len(&self, path: &str) -> Result<usize, Error> {
@@ -277,6 +340,19 @@ impl RedisJSON {
277340
}
278341
}
279342

343+
pub fn get_memory<'a>(&'a self, path: &'a str) -> Result<usize, Error> {
344+
// TODO add better calculation, handle wrappers, internals and length
345+
let res = match self.get_doc(path)? {
346+
Value::Null => 0,
347+
Value::Bool(v) => mem::size_of_val(v),
348+
Value::Number(v) => mem::size_of_val(v),
349+
Value::String(v) => mem::size_of_val(v),
350+
Value::Array(v) => mem::size_of_val(v),
351+
Value::Object(v) => mem::size_of_val(v),
352+
};
353+
Ok(res.into())
354+
}
355+
280356
pub fn get_doc<'a>(&'a self, path: &'a str) -> Result<&'a Value, Error> {
281357
let results = jsonpath_lib::select(&self.data, path)?;
282358
match results.first() {
@@ -292,7 +368,7 @@ pub unsafe extern "C" fn json_rdb_load(rdb: *mut raw::RedisModuleIO, encver: c_i
292368
0 => RedisJSON {
293369
data: backward::json_rdb_load(rdb),
294370
},
295-
2 => RedisJSON::from_str(&raw::load_string(rdb)).unwrap(),
371+
2 => RedisJSON::from_str(&raw::load_string(rdb), Format::JSON).unwrap(),
296372
_ => panic!("Can't load old RedisJSON RDB"),
297373
};
298374
Box::into_raw(Box::new(json)) as *mut c_void

test/files/bson_bytes_1.bson

14 Bytes
Binary file not shown.

test/files/bson_bytes_2.bson

74 Bytes
Binary file not shown.

test/files/pass-cBSON-test5.bson

764 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)