Skip to main content

Module edit

Module edit 

Source
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 at to root and answer the document that results.