Expand description
Changing a document at the places a path matched.
Path::select answers the values a path names and
Value::offset_in turns each of them into a byte offset inside the root,
so a write is a list of offsets and what to do at each one. That is what
edit() takes.
use yo_doc::{Edit, Path, Value, edit, from_json};
let doc = from_json(br#"{"a": {"n": 1}, "b": {"n": 2}}"#)?;
let root = Value::new(&doc).expect("readable");
// Every n in the document, set to 9.
let nine = from_json(b"9")?;
let mut hits = Vec::new();
Path::parse(b"$..n")?.select(&root, &mut hits);
let at: Vec<_> = hits
.iter()
.map(|v| (v.offset_in(&root).expect("from this document"), Edit::Set(&nine)))
.collect();
let after = edit(&root, &at)?;
let out = Value::new(&after).expect("readable");
assert_eq!(out.to_json()?, br#"{"a":{"n":9},"b":{"n":9}}"#);§A document is rebuilt and not patched
Nothing here writes into the bytes it was given. A value’s length lives in
its header and every container above it holds an offset table, so changing
one number from 1 to 1000000000000 moves the end of the document and
every offset between there and the root. Patching that in place is the same
work as rebuilding, with the difference that a rebuild cannot leave a
document half changed if it fails in the middle.
What the rebuild does not do is re-encode. Only the containers on the way
down to a change are opened, and everything else goes through
Builder::embed, which is a memcpy of bytes that
are already in the right form. So a JSON.SET two levels into a hundred
kilobyte document copies a hundred kilobytes and encodes four values, and
the cost follows the size of the document rather than the number of changes.
§An edit inside a value that is going away is dropped
$..a on {"a": {"a": 1}} matches twice and the outer match holds the
inner one. A JSON.DEL with that path is meant to leave nothing behind, and
removing the outer object already removed the inner one, so the inner edit
has nothing left to change and is quietly skipped. The alternative is
refusing a path that a real Redis accepts.
Going away is the important word. A Edit::Set replaces everything below
it and a Edit::Splice replaces only the run it names, so an edit inside
an element the splice keeps still happens, and so does an edit inside a
member an Edit::Put leaves alone. $..* on {"a": [[7], [7, 7]]}
matches the outer array and both inner ones, and JSON.ARRAPPEND with that
path has to reach all three.
An offset that no value in the document begins at is a different thing and is refused, because the only way to hold one is a bug in whatever worked it out, and a write that silently does nothing is the worst way to find out.
Enums§
- Edit
- What to do at one place in a document.
Functions§
- edit
- Apply every edit in
attorootand answer the document that results.