use yo_common::{Code, Error, Result};
use yo_doc::{
Builder, Computed, Edit, Format, Kind, Path, Step, Value, edit, text::write_resp_float,
};
use yo_kv::{Db, Foreign, Keyspace};
use super::args::{self, Args};
use super::table::Spec;
use crate::reply::Out;
const WRONG_TYPE: &[u8] = b"Existing key has wrong Redis type";
const STATIC_PATH: &[u8] = b"Err wrong static path";
const NOT_AT_ROOT: &str = "new objects must be created at the root";
const ROOT: &[u8] = b".";
#[derive(Debug, Default)]
pub(super) struct JsonBody {
doc: Vec<u8>,
}
impl Foreign for JsonBody {
fn type_name(&self) -> &'static str {
"ReJSON-RL"
}
fn encoding(&self) -> &'static str {
"raw"
}
fn memory_bytes(&self) -> usize {
self.doc.capacity()
}
fn is_empty(&self) -> bool {
self.doc.is_empty()
}
}
pub(super) fn execute(db: &Db, spec: &Spec, args: Args<'_>, out: &mut Out) -> Result<()> {
let key = args.get(1);
match spec.name {
"json.mset" => mset(db, args, out),
"json.mget" => mget(db, args, out),
"json.set" => set(&mut db.hold(key), args, out),
"json.merge" => merge(&mut db.hold(key), args, out),
"json.get" => get(&mut db.hold(key), args, out),
"json.resp" => resp(&mut db.hold(key), args, out),
"json.debug" => debug(&mut db.hold(key), args, out),
"json.del" | "json.forget" => del(&mut db.hold(key), args, out),
"json.type" => kind(&mut db.hold(key), args, out),
"json.toggle" => toggle(&mut db.hold(key), args, out),
"json.clear" => clear(&mut db.hold(key), args, out),
"json.arrlen" => sized(&mut db.hold(key), args, out, Asked::ArrayLen),
"json.objlen" => sized(&mut db.hold(key), args, out, Asked::ObjectLen),
"json.strlen" => sized(&mut db.hold(key), args, out, Asked::TextLen),
"json.objkeys" => sized(&mut db.hold(key), args, out, Asked::ObjectKeys),
"json.arrappend" => arrappend(&mut db.hold(key), args, out),
"json.arrinsert" => arrinsert(&mut db.hold(key), args, out),
"json.arrtrim" => arrtrim(&mut db.hold(key), args, out),
"json.arrpop" => arrpop(&mut db.hold(key), args, out),
"json.arrindex" => arrindex(&mut db.hold(key), args, out),
"json.numincrby" => arith(&mut db.hold(key), args, out, Arith::Add),
"json.nummultby" => arith(&mut db.hold(key), args, out, Arith::Mul),
"json.numpowby" => arith(&mut db.hold(key), args, out, Arith::Pow),
"json.strappend" => strappend(&mut db.hold(key), args, out),
other => unreachable!("{other} is not a JSON command"),
}
}
fn set(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
let (key, raw, text) = (args.get(1), args.get(2), args.get(3));
let mut only = Only::Either;
if args.len() == 5 {
if args::is(args.get(4), b"nx") {
only = Only::Missing;
} else if args::is(args.get(4), b"xx") {
only = Only::Present;
} else {
return Err(args::syntax());
}
} else if args.len() != 4 {
return Err(args::wrong_arity("json.set"));
}
let path = path_of(raw)?;
let value = match yo_doc::from_json(text) {
Ok(value) => value,
Err(e) => {
unprefixed(&e, out);
return Ok(());
}
};
match plan_one(db, key, &path, &value, only, out)? {
Plan::Store(doc) => {
store(db, key, doc);
out.ok();
}
Plan::Nothing => out.nil(),
Plan::Refused => {}
}
Ok(())
}
enum Plan {
Store(Vec<u8>),
Nothing,
Refused,
}
fn plan_one<'v>(
db: &mut Keyspace,
key: &[u8],
path: &Path<'v>,
value: &'v [u8],
only: Only,
out: &mut Out,
) -> Result<Plan> {
let body = match doc(db, key, out)? {
Doc::Wrong => return Ok(Plan::Refused),
Doc::Gone => {
if !path.is_root() {
return Err(Error::new(Code::Invalid, NOT_AT_ROOT));
}
if only == Only::Present {
return Ok(Plan::Nothing);
}
return Ok(Plan::Store(value.to_vec()));
}
Doc::Here(body) => body,
};
let root = readable(&body.doc)?;
let mut hits = Vec::new();
path.select(&root, &mut hits);
if hits.is_empty() {
if only == Only::Present {
return Ok(Plan::Nothing);
}
if !path.is_definite() {
out.error(STATIC_PATH);
return Ok(Plan::Refused);
}
let Some(at) = grow(&root, path, value)? else {
return Ok(Plan::Nothing);
};
Ok(Plan::Store(edit(&root, &at)?))
} else {
if only == Only::Missing {
return Ok(Plan::Nothing);
}
let at: Vec<_> = offsets(&root, &hits)?
.into_iter()
.map(|off| (off, Edit::Set(value)))
.collect();
Ok(Plan::Store(edit(&root, &at)?))
}
}
fn store(db: &mut Keyspace, key: &[u8], doc: Vec<u8>) {
if let Ok(Some(body)) = db.foreign_mut(key)
&& let Some(body) = body.downcast_mut::<JsonBody>()
{
body.doc = doc;
return;
}
db.put_foreign(key, Box::new(JsonBody { doc }));
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Only {
Either,
Missing,
Present,
}
fn grow<'v>(
root: &Value<'_>,
path: &Path<'v>,
value: &'v [u8],
) -> Result<Option<Vec<(usize, Edit<'v>)>>> {
let Some((parent, step)) = path.split_last() else {
return Ok(None);
};
let Step::Key(name) = step else {
return Err(Error::new(Code::Invalid, "array index out of range"));
};
let Some(holder) = parent.first(root) else {
return Ok(None);
};
if holder.kind() != Kind::Object {
return Ok(None);
}
Ok(Some(vec![(offset(root, &holder)?, Edit::Put(name, value))]))
}
fn mset(db: &Db, args: Args<'_>, out: &mut Out) -> Result<()> {
if args.len() < 4 || !(args.len() - 1).is_multiple_of(3) {
return Err(args::wrong_arity("json.mset"));
}
let mut jobs = Vec::with_capacity((args.len() - 1) / 3);
for i in (1..args.len()).step_by(3) {
let path = path_of(args.get(i + 1))?;
let value = match yo_doc::from_json(args.get(i + 2)) {
Ok(value) => value,
Err(e) => {
unprefixed(&e, out);
return Ok(());
}
};
jobs.push((args.get(i), path, value));
}
let mut plans = Vec::with_capacity(jobs.len());
for (key, path, value) in &jobs {
match plan_one(&mut db.hold(key), key, path, value, Only::Either, out)? {
Plan::Refused => return Ok(()),
plan => plans.push(plan),
}
}
let mut all = true;
for ((key, _, _), plan) in jobs.iter().zip(plans) {
match plan {
Plan::Store(doc) => store(&mut db.hold(key), key, doc),
Plan::Nothing => all = false,
Plan::Refused => unreachable!("a refused triple is answered before any is written"),
}
}
if all {
out.ok();
} else {
out.nil();
}
Ok(())
}
fn merge(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
if args.len() != 4 {
return Err(args::syntax());
}
let (key, raw, text) = (args.get(1), args.get(2), args.get(3));
let path = path_of(raw)?;
let patch = match yo_doc::from_json(text) {
Ok(patch) => patch,
Err(e) => {
unprefixed(&e, out);
return Ok(());
}
};
let body = match doc_mut(db, key, out)? {
Doc::Wrong => return Ok(()),
Doc::Gone => {
if !path.is_root() {
return Err(Error::new(Code::Invalid, NOT_AT_ROOT));
}
db.put_foreign(key, Box::new(JsonBody { doc: patch }));
out.ok();
return Ok(());
}
Doc::Here(body) => body,
};
let made: Vec<Vec<u8>>;
let after = {
let root = readable(&body.doc)?;
let patched = readable(&patch)?;
let mut hits = Vec::new();
path.select(&root, &mut hits);
if hits.is_empty() {
if !path.is_definite() {
out.error(STATIC_PATH);
return Ok(());
}
let Some(at) = grow(&root, &path, &patch)? else {
out.nil();
return Ok(());
};
edit(&root, &at)?
} else {
made = folded(&root, &hits, &patched)?;
let at: Vec<_> = offsets(&root, &hits)?
.into_iter()
.zip(&made)
.map(|(off, bytes)| (off, Edit::Set(bytes)))
.collect();
edit(&root, &at)?
}
};
body.doc = after;
out.ok();
Ok(())
}
fn folded(root: &Value<'_>, hits: &[Value<'_>], patch: &Value<'_>) -> Result<Vec<Vec<u8>>> {
let spans = hits
.iter()
.map(|v| {
let at = offset(root, v)?;
Ok((at, at + v.encoded_len().ok_or_else(damaged)?))
})
.collect::<Result<Vec<_>>>()?;
let mut order: Vec<usize> = (0..hits.len()).collect();
order.sort_by_key(|&i| core::cmp::Reverse(spans[i].0));
let mut made: Vec<Vec<u8>> = vec![Vec::new(); hits.len()];
for &i in &order {
let (at, end) = spans[i];
let inside: Vec<_> = (0..hits.len())
.filter(|&j| j != i && spans[j].0 > at && spans[j].0 < end)
.map(|j| Ok((offset(&hits[i], &hits[j])?, Edit::Set(made[j].as_slice()))))
.collect::<Result<Vec<_>>>()?;
let with = if inside.is_empty() {
None
} else {
Some(edit(&hits[i], &inside)?)
};
let target = match &with {
Some(bytes) => readable(bytes)?,
None => hits[i],
};
let mut b = Builder::new();
merged(Some(&target), patch, &mut b)?;
made[i] = b.finish()?.to_vec();
}
Ok(made)
}
fn merged(target: Option<&Value<'_>>, patch: &Value<'_>, b: &mut Builder) -> Result<()> {
if patch.kind() != Kind::Object {
return b.embed(patch);
}
let was = target.filter(|t| t.kind() == Kind::Object);
b.begin_object()?;
if let Some(t) = was {
for i in 0..t.len() {
let key = t.key_at(i).ok_or_else(damaged)?;
if patch.get(key).is_some() {
continue;
}
b.key(key)?;
b.embed(&t.at(i).ok_or_else(damaged)?)?;
}
}
for i in 0..patch.len() {
let key = patch.key_at(i).ok_or_else(damaged)?;
let v = patch.at(i).ok_or_else(damaged)?;
if v.is_null() {
continue;
}
b.key(key)?;
merged(was.and_then(|t| t.get(key)).as_ref(), &v, b)?;
}
b.end_object()
}
fn del(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
let key = args.get(1);
let raw = args.opt(2).unwrap_or(ROOT);
let path = path_of(raw)?;
let body = match doc_mut(db, key, out)? {
Doc::Wrong => return Ok(()),
Doc::Gone => {
out.int(0);
return Ok(());
}
Doc::Here(body) => body,
};
if path.is_root() {
body.doc.clear();
db.reap_foreign(key);
out.int(1);
return Ok(());
}
let (after, gone) = {
let root = readable(&body.doc)?;
let mut hits = Vec::new();
path.select(&root, &mut hits);
if hits.is_empty() {
out.int(0);
return Ok(());
}
let at: Vec<_> = offsets(&root, &hits)?
.into_iter()
.map(|off| (off, Edit::Remove))
.collect();
(edit(&root, &at)?, at.len())
};
let empty_root = matches!(
readable(&after).map(|v| (v.kind(), v.is_empty())),
Ok((Kind::Object | Kind::Array, true))
);
body.doc = after;
if empty_root {
body.doc.clear();
db.reap_foreign(key);
}
out.int(gone as i64);
Ok(())
}
fn toggle(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
let (key, raw) = (args.get(1), args.get(2));
let path = path_of(raw)?;
let body = match doc_mut(db, key, out)? {
Doc::Wrong => return Ok(()),
Doc::Gone => return Err(no_key()),
Doc::Here(body) => body,
};
let (after, flipped) = {
let root = readable(&body.doc)?;
let mut hits = Vec::new();
path.select(&root, &mut hits);
let (yes, no) = (yo_doc::from_json(b"true")?, yo_doc::from_json(b"false")?);
let mut at = Vec::new();
let mut flipped = Vec::new();
for v in &hits {
match v.as_bool() {
Some(was) => {
let now: &[u8] = if was { &no } else { &yes };
at.push((offset(&root, v)?, Edit::Set(now)));
flipped.push(Some(!was));
}
None => flipped.push(None),
}
}
if path.legacy() && !flipped.iter().any(Option::is_some) {
return Err(not_a_bool());
}
(edit(&root, &at)?, flipped)
};
body.doc = after;
if path.legacy() {
match Pick::Last.of(&flipped) {
Some(now) => out.bulk(if *now { b"true" } else { b"false" }),
None => out.nil(),
}
return Ok(());
}
out.array(flipped.len());
for f in flipped {
match f {
Some(now) => out.int(i64::from(now)),
None => out.nil(),
}
}
Ok(())
}
fn clear(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
let key = args.get(1);
let raw = args.opt(2).unwrap_or(ROOT);
let path = path_of(raw)?;
let body = match doc_mut(db, key, out)? {
Doc::Wrong => return Ok(()),
Doc::Gone => return Err(no_key()),
Doc::Here(body) => body,
};
let (after, cleared) = {
let root = readable(&body.doc)?;
let mut hits = Vec::new();
path.select(&root, &mut hits);
let (empty_object, empty_array, zero) = (
yo_doc::from_json(b"{}")?,
yo_doc::from_json(b"[]")?,
yo_doc::from_json(b"0")?,
);
let mut at = Vec::new();
for v in &hits {
let to: &[u8] = match v.kind() {
Kind::Object if v.is_empty() => continue,
Kind::Array if v.is_empty() => continue,
Kind::Object => &empty_object,
Kind::Array => &empty_array,
Kind::Int if v.as_int() == Some(0) => continue,
Kind::Int | Kind::Float => &zero,
_ => continue,
};
at.push((offset(&root, v)?, Edit::Set(to)));
}
(edit(&root, &at)?, at.len())
};
body.doc = after;
out.int(cleared as i64);
Ok(())
}
fn get(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
let key = args.get(1);
let (f, from) = format(args, 2)?;
let raws: Vec<&[u8]> = if from < args.len() {
(from..args.len()).map(|i| args.get(i)).collect()
} else {
vec![ROOT]
};
let paths: Vec<Path<'_>> = raws.iter().map(|r| Path::parse(r)).collect::<Result<_>>()?;
let body = match doc(db, key, out)? {
Doc::Wrong => return Ok(()),
Doc::Gone => {
out.nil();
return Ok(());
}
Doc::Here(body) => body,
};
let root = readable(&body.doc)?;
let mut text = Vec::new();
if paths.len() == 1 {
one(&root, &paths[0], &f, &mut text, 0)?;
} else {
text.push(b'{');
for (i, (path, raw)) in paths.iter().zip(&raws).enumerate() {
if i > 0 {
text.push(b',');
}
line(&f, &mut text, 1);
quote(raw, &mut text);
text.push(b':');
text.extend_from_slice(f.space);
one(&root, path, &f, &mut text, 1)?;
}
line(&f, &mut text, 0);
text.push(b'}');
}
out.bulk(&text);
Ok(())
}
fn path_of(raw: &[u8]) -> Result<Path<'_>> {
let path = Path::parse(raw)?;
if path.is_projection() {
return Err(Error::new(
Code::Invalid,
"computed/projection expressions are only supported by JSON.GET/JSON.MGET/JSON.RESP",
));
}
Ok(path)
}
fn one<'d>(
root: &Value<'d>,
path: &'d Path<'d>,
f: &Format<'_>,
text: &mut Vec<u8>,
depth: usize,
) -> Result<()> {
if path.is_projection() {
let got = path.project(root);
let laid_out = !f.is_plain() && !got.is_empty();
text.push(b'[');
for (i, v) in got.iter().enumerate() {
if i > 0 {
text.push(b',');
}
if laid_out {
line(f, text, depth + 1);
}
v.write_json_at(f, text, depth + 1)?;
}
if laid_out {
line(f, text, depth);
}
text.push(b']');
return Ok(());
}
let mut hits = Vec::new();
path.select(root, &mut hits);
if path.legacy() {
let Some(v) = hits.first() else {
return Err(missing());
};
return v.write_json_at(f, text, depth);
}
let laid_out = !f.is_plain() && !hits.is_empty();
text.push(b'[');
for (i, v) in hits.iter().enumerate() {
if i > 0 {
text.push(b',');
}
if laid_out {
line(f, text, depth + 1);
}
v.write_json_at(f, text, depth + 1)?;
}
if laid_out {
line(f, text, depth);
}
text.push(b']');
Ok(())
}
fn line(f: &Format<'_>, text: &mut Vec<u8>, depth: usize) {
if f.is_plain() {
return;
}
text.extend_from_slice(f.newline);
for _ in 0..depth {
text.extend_from_slice(f.indent);
}
}
fn mget(db: &Db, args: Args<'_>, out: &mut Out) -> Result<()> {
let last = args.len() - 1;
let path = Path::parse(args.get(last))?;
let f = Format::default();
out.array(last - 1);
let mut text = Vec::new();
for i in 1..last {
let key = args.get(i);
let mut stripe = db.hold(key);
let Ok(Some(body)) = stripe.foreign(key) else {
out.nil();
continue;
};
let Some(body) = body.downcast_ref::<JsonBody>() else {
out.nil();
continue;
};
let root = readable(&body.doc)?;
text.clear();
match one(&root, &path, &f, &mut text, 0) {
Ok(()) => out.bulk(&text),
Err(_) => out.nil(),
}
}
Ok(())
}
fn kind(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
let key = args.get(1);
let raw = args.opt(2).unwrap_or(ROOT);
let path = path_of(raw)?;
let body = match doc(db, key, out)? {
Doc::Wrong => return Ok(()),
Doc::Gone => {
out.nil();
return Ok(());
}
Doc::Here(body) => body,
};
let root = readable(&body.doc)?;
let mut hits = Vec::new();
path.select(&root, &mut hits);
if path.legacy() {
match hits.first() {
Some(v) => out.bulk(word(v)),
None => out.nil(),
}
return Ok(());
}
out.array(hits.len());
for v in &hits {
out.bulk(word(v));
}
Ok(())
}
fn word(v: &Value<'_>) -> &'static [u8] {
match v.kind() {
Kind::Null => b"null",
Kind::Bool => b"boolean",
Kind::Int => b"integer",
Kind::Float => b"number",
Kind::Text => b"string",
Kind::Array => b"array",
Kind::Object => b"object",
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Asked {
ArrayLen,
ObjectLen,
TextLen,
ObjectKeys,
}
impl Asked {
fn kind(self) -> Kind {
match self {
Asked::ArrayLen => Kind::Array,
Asked::ObjectLen | Asked::ObjectKeys => Kind::Object,
Asked::TextLen => Kind::Text,
}
}
fn quiet(self) -> bool {
matches!(self, Asked::ObjectLen | Asked::ObjectKeys)
}
fn wrong(self) -> Error {
match self {
Asked::ArrayLen => Error::new(Code::Invalid, "Path does not exist or not an array"),
Asked::ObjectKeys => Error::new(Code::Invalid, "Path does not exist or not an object"),
Asked::ObjectLen => Error::new(
Code::WrongType,
"wrong type of path value - expected object",
),
Asked::TextLen => Error::new(
Code::WrongType,
"wrong type of path value - expected string",
),
}
}
fn no_key(self) -> Error {
match self {
Asked::ObjectLen => Error::new(Code::Invalid, "Path does not exist or not an object"),
_ => no_key(),
}
}
fn one(self, v: &Value<'_>, out: &mut Out) {
match self {
Asked::TextLen => out.int(v.text_bytes().unwrap_or_default().len() as i64),
Asked::ArrayLen | Asked::ObjectLen => out.int(v.len() as i64),
Asked::ObjectKeys => {
out.array(v.len());
for i in 0..v.len() {
out.bulk(v.key_at(i).unwrap_or_default());
}
}
}
}
}
fn sized(db: &mut Keyspace, args: Args<'_>, out: &mut Out, asked: Asked) -> Result<()> {
let key = args.get(1);
let raw = args.opt(2).unwrap_or(ROOT);
let path = path_of(raw)?;
let body = match doc(db, key, out)? {
Doc::Wrong => return Ok(()),
Doc::Gone if path.legacy() => {
out.nil();
return Ok(());
}
Doc::Gone => return Err(asked.no_key()),
Doc::Here(body) => body,
};
let root = readable(&body.doc)?;
let mut hits = Vec::new();
path.select(&root, &mut hits);
if path.legacy() {
let Some(v) = hits.first() else {
if asked.quiet() {
out.nil();
return Ok(());
}
return Err(missing());
};
if v.kind() != asked.kind() {
return Err(asked.wrong());
}
asked.one(v, out);
return Ok(());
}
out.array(hits.len());
for v in &hits {
if v.kind() == asked.kind() {
asked.one(v, out);
} else {
out.nil();
}
}
Ok(())
}
fn not_an_array() -> Error {
Error::new(Code::Invalid, "Path does not exist or not an array")
}
fn arrays<'r>(root: &Value<'r>, path: &Path<'_>) -> Result<Vec<Option<(usize, Value<'r>)>>> {
let mut hits = Vec::new();
path.select(root, &mut hits);
let mut out = Vec::with_capacity(hits.len());
for v in hits {
if v.kind() == Kind::Array {
out.push(Some((offset(root, &v)?, v)));
} else {
out.push(None);
}
}
Ok(out)
}
fn any_array(found: &[Option<(usize, Value<'_>)>]) -> Result<()> {
if found.iter().any(Option::is_some) {
return Ok(());
}
Err(not_an_array())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Pick {
First,
Last,
}
impl Pick {
fn of<T>(self, all: &[Option<T>]) -> Option<&T> {
let mut kept = all.iter().flatten();
match self {
Pick::First => kept.next(),
Pick::Last => kept.next_back(),
}
}
}
fn lengths(path: &Path<'_>, lens: &[Option<usize>], pick: Pick, out: &mut Out) {
if path.legacy() {
match pick.of(lens) {
Some(n) => out.int(*n as i64),
None => out.nil(),
}
return;
}
out.array(lens.len());
for n in lens {
match n {
Some(n) => out.int(*n as i64),
None => out.nil(),
}
}
}
fn values(args: Args<'_>, from: usize) -> Result<Vec<Vec<u8>>> {
(from..args.len())
.map(|i| yo_doc::from_json(args.get(i)))
.collect()
}
fn arrappend(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
let (key, raw) = (args.get(1), args.get(2));
if args.len() < 4 {
return Err(args::wrong_arity("json.arrappend"));
}
let path = path_of(raw)?;
let added = match values(args, 3) {
Ok(added) => added,
Err(e) => {
unprefixed(&e, out);
return Ok(());
}
};
let body = match doc_mut(db, key, out)? {
Doc::Wrong => return Ok(()),
Doc::Gone => return Err(no_key()),
Doc::Here(body) => body,
};
let put: Vec<&[u8]> = added.iter().map(Vec::as_slice).collect();
let (after, lens) = {
let root = readable(&body.doc)?;
let found = arrays(&root, &path)?;
if path.legacy() {
any_array(&found)?;
}
let mut at = Vec::new();
let mut lens = Vec::with_capacity(found.len());
for f in &found {
match f {
Some((off, v)) => {
at.push((
*off,
Edit::Splice {
at: v.len(),
take: 0,
put: &put,
},
));
lens.push(Some(v.len() + put.len()));
}
None => lens.push(None),
}
}
(edit(&root, &at)?, lens)
};
body.doc = after;
lengths(&path, &lens, Pick::Last, out);
Ok(())
}
fn arrinsert(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
let (key, raw) = (args.get(1), args.get(2));
if args.len() < 5 {
return Err(args::wrong_arity("json.arrinsert"));
}
let path = path_of(raw)?;
let want = args.int(3)?;
let added = match values(args, 4) {
Ok(added) => added,
Err(e) => {
unprefixed(&e, out);
return Ok(());
}
};
let body = match doc_mut(db, key, out)? {
Doc::Wrong => return Ok(()),
Doc::Gone => return Err(no_key()),
Doc::Here(body) => body,
};
let put: Vec<&[u8]> = added.iter().map(Vec::as_slice).collect();
let (after, lens) = {
let root = readable(&body.doc)?;
let found = arrays(&root, &path)?;
if path.legacy() {
any_array(&found)?;
}
let mut at = Vec::new();
let mut lens = Vec::with_capacity(found.len());
for f in &found {
match f {
Some((off, v)) => {
let Some(i) = place(want, v.len()) else {
return Err(Error::new(Code::Invalid, "index out of bounds"));
};
at.push((
*off,
Edit::Splice {
at: i,
take: 0,
put: &put,
},
));
lens.push(Some(v.len() + put.len()));
}
None => lens.push(None),
}
}
(edit(&root, &at)?, lens)
};
body.doc = after;
lengths(&path, &lens, Pick::First, out);
Ok(())
}
fn place(want: i64, len: usize) -> Option<usize> {
let at = if want < 0 { len as i64 + want } else { want };
if at < 0 || at > len as i64 {
return None;
}
Some(at as usize)
}
fn arrtrim(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
let (key, raw) = (args.get(1), args.get(2));
if args.len() != 5 {
return Err(args::wrong_arity("json.arrtrim"));
}
let path = path_of(raw)?;
let (from, to) = (args.int(3)?, args.int(4)?);
let body = match doc_mut(db, key, out)? {
Doc::Wrong => return Ok(()),
Doc::Gone => return Err(no_key()),
Doc::Here(body) => body,
};
let (after, lens) = {
let root = readable(&body.doc)?;
let found = arrays(&root, &path)?;
if path.legacy() {
any_array(&found)?;
}
let mut kept: Vec<Vec<&[u8]>> = Vec::with_capacity(found.len());
for f in &found {
let mut keep = Vec::new();
if let Some((_, v)) = f {
let (start, stop) = span(from, to, v.len());
for i in start..stop {
if let Some(e) = v.at(i).and_then(|e| e.as_bytes()) {
keep.push(e);
}
}
}
kept.push(keep);
}
let mut at = Vec::new();
let mut lens = Vec::with_capacity(found.len());
for (f, keep) in found.iter().zip(&kept) {
match f {
Some((off, v)) => {
at.push((
*off,
Edit::Splice {
at: 0,
take: v.len(),
put: keep,
},
));
lens.push(Some(keep.len()));
}
None => lens.push(None),
}
}
(edit(&root, &at)?, lens)
};
body.doc = after;
lengths(&path, &lens, Pick::First, out);
Ok(())
}
fn span(from: i64, to: i64, len: usize) -> (usize, usize) {
let n = len as i64;
let start = if from < 0 {
(n + from).max(0)
} else {
from.min(n)
};
let stop = if to < 0 {
(n + to).max(0)
} else {
to.min(n - 1)
};
if start > stop || len == 0 {
return (0, 0);
}
(start as usize, stop as usize + 1)
}
fn arrpop(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
let key = args.get(1);
let raw = args.opt(2).unwrap_or(ROOT);
if args.len() > 4 {
return Err(args::wrong_arity("json.arrpop"));
}
let path = path_of(raw)?;
let want = if args.len() == 4 { args.int(3)? } else { -1 };
let body = match doc_mut(db, key, out)? {
Doc::Wrong => return Ok(()),
Doc::Gone => return Err(no_key()),
Doc::Here(body) => body,
};
let (after, gone) = {
let root = readable(&body.doc)?;
let found = arrays(&root, &path)?;
if path.legacy() {
any_array(&found)?;
}
let mut at = Vec::new();
let mut gone: Vec<Option<Option<Vec<u8>>>> = Vec::with_capacity(found.len());
for f in &found {
match f {
Some((_, v)) if v.is_empty() => gone.push(Some(None)),
Some((off, v)) => {
let i = reach(want, v.len());
let mut text = Vec::new();
match v.at(i) {
Some(e) => e.write_json(&mut text)?,
None => return Err(missing()),
}
at.push((
*off,
Edit::Splice {
at: i,
take: 1,
put: &[],
},
));
gone.push(Some(Some(text)));
}
None => gone.push(None),
}
}
(edit(&root, &at)?, gone)
};
body.doc = after;
if path.legacy() {
match Pick::First.of(&gone) {
Some(Some(text)) => out.bulk(text),
_ => out.nil(),
}
return Ok(());
}
out.array(gone.len());
for g in &gone {
match g {
Some(Some(text)) => out.bulk(text),
_ => out.nil(),
}
}
Ok(())
}
fn reach(want: i64, len: usize) -> usize {
let n = len as i64;
let at = if want < 0 { n + want } else { want };
at.clamp(0, n - 1) as usize
}
fn arrindex(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
let (key, raw, text) = (args.get(1), args.get(2), args.get(3));
if args.len() < 4 || args.len() > 6 {
return Err(args::wrong_arity("json.arrindex"));
}
let path = path_of(raw)?;
let looking = yo_doc::from_json(text)?;
let from = if args.len() > 4 { args.int(4)? } else { 0 };
let to = if args.len() > 5 { args.int(5)? } else { 0 };
let body = match doc(db, key, out)? {
Doc::Wrong => return Ok(()),
Doc::Gone => return Err(missing()),
Doc::Here(body) => body,
};
let root = readable(&body.doc)?;
let looking = readable(&looking)?;
let mut hits = Vec::new();
path.select(&root, &mut hits);
if path.legacy() {
let Some(v) = hits.first() else {
return Err(missing());
};
if v.kind() != Kind::Array {
return Err(Error::new(
Code::WrongType,
"wrong type of path value - expected array",
));
}
out.int(seek(v, &looking, from, to));
return Ok(());
}
out.array(hits.len());
for v in &hits {
if v.kind() == Kind::Array {
out.int(seek(v, &looking, from, to));
} else {
out.nil();
}
}
Ok(())
}
fn seek(v: &Value<'_>, looking: &Value<'_>, from: i64, to: i64) -> i64 {
let n = v.len() as i64;
let start = if from < 0 { n + from } else { from }.clamp(0, (n - 1).max(0));
let stop = if to == 0 {
n
} else if to < 0 {
(n + to).max(0)
} else {
to.min(n)
};
let mut i = start;
while i < stop {
if let Some(e) = v.at(i as usize)
&& same(&e, looking)
{
return i;
}
i += 1;
}
-1
}
fn same(a: &Value<'_>, b: &Value<'_>) -> bool {
if a.kind() != b.kind() {
return false;
}
match a.kind() {
Kind::Null => true,
Kind::Bool => a.as_bool() == b.as_bool(),
Kind::Int => a.as_int() == b.as_int(),
Kind::Float => a.as_float() == b.as_float(),
Kind::Text => a.text_bytes() == b.text_bytes(),
Kind::Array => {
a.len() == b.len()
&& (0..a.len()).all(|i| match (a.at(i), b.at(i)) {
(Some(x), Some(y)) => same(&x, &y),
_ => false,
})
}
Kind::Object => {
a.len() == b.len()
&& (0..a.len()).all(|i| {
let key = a.key_at(i);
match (key, key.and_then(|k| b.get(k)), a.at(i)) {
(Some(_), Some(y), Some(x)) => same(&x, &y),
_ => false,
}
})
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Arith {
Add,
Mul,
Pow,
}
#[derive(Debug, Clone, Copy)]
enum Num {
Int(i64),
Float(f64),
}
impl Num {
fn of(v: &Value<'_>) -> Option<Num> {
match v.kind() {
Kind::Int => v.as_int().map(Num::Int),
Kind::Float => v.as_float().map(Num::Float),
_ => None,
}
}
fn as_f64(self) -> f64 {
match self {
Num::Int(i) => i as f64,
Num::Float(f) => f,
}
}
fn encode(self) -> Result<Vec<u8>> {
let mut b = Builder::new();
match self {
Num::Int(i) => b.int(i)?,
Num::Float(f) => b.float(f)?,
}
Ok(b.finish()?.to_vec())
}
}
impl Arith {
fn apply(self, a: Num, b: Num) -> Result<Num> {
if let (Num::Int(x), Num::Int(y)) = (a, b) {
let done = match self {
Arith::Add => x.checked_add(y),
Arith::Mul => x.checked_mul(y),
Arith::Pow => u32::try_from(y).ok().and_then(|y| x.checked_pow(y)),
};
return done.map(Num::Int).ok_or_else(overflowed);
}
let (x, y) = (a.as_f64(), b.as_f64());
let done = match self {
Arith::Add => x + y,
Arith::Mul => x * y,
Arith::Pow => x.powf(y),
};
if !done.is_finite() {
return Err(not_a_number());
}
Ok(Num::Float(done))
}
}
fn arith(db: &mut Keyspace, args: Args<'_>, out: &mut Out, how: Arith) -> Result<()> {
let (key, raw, operand) = (args.get(1), args.get(2), args.get(3));
let path = path_of(raw)?;
let body = match doc_mut(db, key, out)? {
Doc::Wrong => return Ok(()),
Doc::Gone => return Err(no_key()),
Doc::Here(body) => body,
};
let mut made: Vec<Option<Vec<u8>>> = Vec::new();
let after = {
let root = readable(&body.doc)?;
let mut hits = Vec::new();
path.select(&root, &mut hits);
let was: Vec<Option<Num>> = hits.iter().map(Num::of).collect();
let holder;
let by = if was.iter().any(Option::is_some) {
holder = yo_doc::from_json(operand)?;
match Num::of(&readable(&holder)?) {
Some(by) => Some(by),
None => {
out.error(b"bad input number");
return Ok(());
}
}
} else if path.legacy() {
return Err(no_number());
} else {
None
};
for w in &was {
match (w, by) {
(Some(w), Some(by)) => made.push(Some(how.apply(*w, by)?.encode()?)),
_ => made.push(None),
}
}
let mut at = Vec::new();
for (v, m) in hits.iter().zip(&made) {
if let Some(bytes) = m {
at.push((offset(&root, v)?, Edit::Set(bytes)));
}
}
edit(&root, &at)?
};
body.doc = after;
let mut text = Vec::new();
if path.legacy() {
if let Some(bytes) = Pick::Last.of(&made) {
readable(bytes)?.write_json(&mut text)?;
}
out.bulk(&text);
return Ok(());
}
text.push(b'[');
for (i, m) in made.iter().enumerate() {
if i > 0 {
text.push(b',');
}
match m {
Some(bytes) => readable(bytes)?.write_json(&mut text)?,
None => text.extend_from_slice(b"null"),
}
}
text.push(b']');
out.bulk(&text);
Ok(())
}
fn strappend(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
let key = args.get(1);
let (raw, text) = if args.len() == 3 {
(ROOT, args.get(2))
} else {
(args.get(2), args.get(3))
};
let path = path_of(raw)?;
let body = match doc_mut(db, key, out)? {
Doc::Wrong => return Ok(()),
Doc::Gone => return Err(no_key()),
Doc::Here(body) => body,
};
let mut made: Vec<Option<Vec<u8>>> = Vec::new();
let after = {
let root = readable(&body.doc)?;
let mut hits = Vec::new();
path.select(&root, &mut hits);
let was: Vec<Option<&[u8]>> = hits.iter().map(Value::text_bytes).collect();
let holder;
let more = if was.iter().any(Option::is_some) {
holder = yo_doc::from_json(text)?;
match readable(&holder)?.text_bytes() {
Some(more) => Some(more),
None => {
return Err(Error::new(
Code::WrongType,
"wrong type of path value - expected string",
));
}
}
} else if path.legacy() {
return Err(not_a_string());
} else {
None
};
for w in &was {
match (w, more) {
(Some(was), Some(more)) => {
let mut joined = Vec::with_capacity(was.len() + more.len());
joined.extend_from_slice(was);
joined.extend_from_slice(more);
let mut b = Builder::new();
b.text_bytes(&joined)?;
made.push(Some(b.finish()?.to_vec()));
}
_ => made.push(None),
}
}
let mut at = Vec::new();
for (v, m) in hits.iter().zip(&made) {
if let Some(bytes) = m {
at.push((offset(&root, v)?, Edit::Set(bytes)));
}
}
edit(&root, &at)?
};
body.doc = after;
let lens: Vec<Option<usize>> = made
.iter()
.map(|m| {
m.as_ref()
.and_then(|b| Value::new(b))
.and_then(|v| v.text_bytes())
.map(<[u8]>::len)
})
.collect();
lengths(&path, &lens, Pick::Last, out);
Ok(())
}
fn resp(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
let key = args.get(1);
let raw = args.opt(2).unwrap_or(ROOT);
let path = Path::parse(raw)?;
let body = match doc(db, key, out)? {
Doc::Wrong => return Ok(()),
Doc::Gone => {
out.nil();
return Ok(());
}
Doc::Here(body) => body,
};
let root = readable(&body.doc)?;
if path.is_projection() {
let got = path.project(&root);
out.array(got.len());
for v in &got {
computed(v, out)?;
}
return Ok(());
}
let mut hits = Vec::new();
path.select(&root, &mut hits);
if path.legacy() {
let Some(v) = hits.first() else {
return Err(missing());
};
return shape(v, out);
}
out.array(hits.len());
for v in &hits {
shape(v, out)?;
}
Ok(())
}
fn computed(v: &Computed<'_>, out: &mut Out) -> Result<()> {
match v {
Computed::Value(v) => shape(v, out),
Computed::Name(k) => {
out.bulk(k);
Ok(())
}
Computed::Int(i) => {
out.int(*i);
Ok(())
}
Computed::Float(x) => {
let mut text = Vec::new();
write_resp_float(*x, &mut text);
out.bulk(&text);
Ok(())
}
}
}
fn shape(v: &Value<'_>, out: &mut Out) -> Result<()> {
match v.kind() {
Kind::Null => out.nil(),
Kind::Bool => out.simple(if v.as_bool() == Some(true) {
b"true"
} else {
b"false"
}),
Kind::Int => out.int(v.as_int().ok_or_else(damaged)?),
Kind::Float => {
let mut text = Vec::new();
write_resp_float(v.as_float().ok_or_else(damaged)?, &mut text);
out.bulk(&text);
}
Kind::Text => out.bulk(v.text_bytes().ok_or_else(damaged)?),
Kind::Array => {
out.array(v.len() + 1);
out.simple(b"[");
for e in v.iter() {
shape(&e, out)?;
}
}
Kind::Object => {
out.array(v.len() * 2 + 1);
out.simple(b"{");
for i in 0..v.len() {
out.bulk(v.key_at(i).ok_or_else(damaged)?);
shape(&v.at(i).ok_or_else(damaged)?, out)?;
}
}
}
Ok(())
}
fn debug(db: &mut Keyspace, args: Args<'_>, out: &mut Out) -> Result<()> {
if args::is(args.get(1), b"help") {
out.array(2);
out.bulk(b"MEMORY <key> [path] - reports memory usage");
out.bulk(b"HELP - this message");
return Ok(());
}
if !args::is(args.get(1), b"memory") {
return Err(Error::new(
Code::Invalid,
"unknown subcommand - try `JSON.DEBUG HELP`",
));
}
if args.len() < 3 {
return Err(args::wrong_arity("json.debug"));
}
let key = args.get(2);
let raw = args.opt(3).unwrap_or(ROOT);
let path = path_of(raw)?;
let body = match doc(db, key, out)? {
Doc::Wrong => return Ok(()),
Doc::Gone => {
if path.legacy() {
out.int(0);
} else {
out.array(0);
}
return Ok(());
}
Doc::Here(body) => body,
};
let root = readable(&body.doc)?;
let mut hits = Vec::new();
path.select(&root, &mut hits);
if path.legacy() {
let Some(v) = hits.first() else {
return Err(missing());
};
out.int(i64::try_from(v.encoded_len().ok_or_else(damaged)?).unwrap_or(i64::MAX));
return Ok(());
}
out.array(hits.len());
for v in &hits {
out.int(i64::try_from(v.encoded_len().ok_or_else(damaged)?).unwrap_or(i64::MAX));
}
Ok(())
}
enum Doc<B> {
Here(B),
Gone,
Wrong,
}
fn doc_mut<'d>(db: &'d mut Keyspace, key: &[u8], out: &mut Out) -> Result<Doc<&'d mut JsonBody>> {
match db.foreign_mut(key) {
Ok(Some(body)) => match body.downcast_mut::<JsonBody>() {
Some(body) => Ok(Doc::Here(body)),
None => {
out.error(WRONG_TYPE);
Ok(Doc::Wrong)
}
},
Ok(None) => Ok(Doc::Gone),
Err(e) if e.code() == Code::WrongType => {
out.error(WRONG_TYPE);
Ok(Doc::Wrong)
}
Err(e) => Err(e),
}
}
fn doc<'d>(db: &'d mut Keyspace, key: &[u8], out: &mut Out) -> Result<Doc<&'d JsonBody>> {
match db.foreign(key) {
Ok(Some(body)) => match body.downcast_ref::<JsonBody>() {
Some(body) => Ok(Doc::Here(body)),
None => {
out.error(WRONG_TYPE);
Ok(Doc::Wrong)
}
},
Ok(None) => Ok(Doc::Gone),
Err(e) if e.code() == Code::WrongType => {
out.error(WRONG_TYPE);
Ok(Doc::Wrong)
}
Err(e) => Err(e),
}
}
fn format(args: Args<'_>, from: usize) -> Result<(Format<'_>, usize)> {
let mut f = Format::default();
let mut i = from;
while i + 1 < args.len() {
let arg = args.get(i);
if args::is(arg, b"indent") {
f.indent = args.get(i + 1);
} else if args::is(arg, b"newline") {
f.newline = args.get(i + 1);
} else if args::is(arg, b"space") {
f.space = args.get(i + 1);
} else {
break;
}
i += 2;
}
Ok((f, i))
}
fn quote(raw: &[u8], out: &mut Vec<u8>) {
let mut b = Builder::new();
if b.text_bytes(raw).is_ok()
&& let Ok(bytes) = b.finish()
&& let Some(v) = Value::new(bytes)
&& v.write_json(out).is_ok()
{
return;
}
out.extend_from_slice(b"\"\"");
}
fn offsets(root: &Value<'_>, hits: &[Value<'_>]) -> Result<Vec<usize>> {
hits.iter().map(|v| offset(root, v)).collect()
}
fn offset(root: &Value<'_>, v: &Value<'_>) -> Result<usize> {
v.offset_in(root).ok_or_else(|| {
Error::new(
Code::Invalid,
"a path matched a value that is not in the document it was matched against",
)
})
}
fn readable(doc: &[u8]) -> Result<Value<'_>> {
Value::new(doc).ok_or_else(damaged)
}
fn damaged() -> Error {
Error::new(Code::Invalid, "the document stored here is damaged")
}
fn unprefixed(e: &Error, out: &mut Out) {
out.error_line(b"", e.message().as_bytes());
}
fn missing() -> Error {
Error::new(Code::Invalid, "Path does not exist")
}
fn not_a_bool() -> Error {
Error::new(Code::Invalid, "Path does not exist or not a bool")
}
fn no_number() -> Error {
Error::new(
Code::Invalid,
"Path does not exist or does not contains a number",
)
}
fn not_a_string() -> Error {
Error::new(Code::Invalid, "Path does not exist or not a string")
}
fn overflowed() -> Error {
Error::new(Code::Invalid, "numeric overflow")
}
fn not_a_number() -> Error {
Error::new(Code::Invalid, "result is not a number")
}
fn no_key() -> Error {
Error::new(
Code::Invalid,
"could not perform this operation on a key that doesn't exist",
)
}