Skip to main content

yo_doc/
edit.rs

1//! Changing a document at the places a path matched.
2//!
3//! [`Path::select`](crate::Path::select) answers the values a path names and
4//! [`Value::offset_in`] turns each of them into a byte offset inside the root,
5//! so a write is a list of offsets and what to do at each one. That is what
6//! [`edit()`] takes.
7//!
8//! ```
9//! use yo_doc::{Edit, Path, Value, edit, from_json};
10//!
11//! let doc = from_json(br#"{"a": {"n": 1}, "b": {"n": 2}}"#)?;
12//! let root = Value::new(&doc).expect("readable");
13//!
14//! // Every n in the document, set to 9.
15//! let nine = from_json(b"9")?;
16//! let mut hits = Vec::new();
17//! Path::parse(b"$..n")?.select(&root, &mut hits);
18//! let at: Vec<_> = hits
19//!     .iter()
20//!     .map(|v| (v.offset_in(&root).expect("from this document"), Edit::Set(&nine)))
21//!     .collect();
22//!
23//! let after = edit(&root, &at)?;
24//! let out = Value::new(&after).expect("readable");
25//! assert_eq!(out.to_json()?, br#"{"a":{"n":9},"b":{"n":9}}"#);
26//! # Ok::<(), yo_common::Error>(())
27//! ```
28//!
29//! # A document is rebuilt and not patched
30//!
31//! Nothing here writes into the bytes it was given. A value's length lives in
32//! its header and every container above it holds an offset table, so changing
33//! one number from `1` to `1000000000000` moves the end of the document and
34//! every offset between there and the root. Patching that in place is the same
35//! work as rebuilding, with the difference that a rebuild cannot leave a
36//! document half changed if it fails in the middle.
37//!
38//! What the rebuild does not do is re-encode. Only the containers on the way
39//! down to a change are opened, and everything else goes through
40//! [`Builder::embed`](crate::Builder::embed), which is a memcpy of bytes that
41//! are already in the right form. So a `JSON.SET` two levels into a hundred
42//! kilobyte document copies a hundred kilobytes and encodes four values, and
43//! the cost follows the size of the document rather than the number of changes.
44//!
45//! # An edit inside a value that is going away is dropped
46//!
47//! `$..a` on `{"a": {"a": 1}}` matches twice and the outer match holds the
48//! inner one. A `JSON.DEL` with that path is meant to leave nothing behind, and
49//! removing the outer object already removed the inner one, so the inner edit
50//! has nothing left to change and is quietly skipped. The alternative is
51//! refusing a path that a real Redis accepts.
52//!
53//! Going away is the important word. A [`Edit::Set`] replaces everything below
54//! it and a [`Edit::Splice`] replaces only the run it names, so an edit inside
55//! an element the splice keeps still happens, and so does an edit inside a
56//! member an [`Edit::Put`] leaves alone. `$..*` on `{"a": [[7], [7, 7]]}`
57//! matches the outer array and both inner ones, and `JSON.ARRAPPEND` with that
58//! path has to reach all three.
59//!
60//! An offset that no value in the document begins at is a different thing and
61//! is refused, because the only way to hold one is a bug in whatever worked it
62//! out, and a write that silently does nothing is the worst way to find out.
63
64use yo_common::{Code, Error, Result};
65
66use crate::build::Builder;
67use crate::head::Kind;
68use crate::read::Value;
69
70/// What to do at one place in a document.
71///
72/// Every set of bytes here is an encoded value and not JSON text. The callers
73/// are the `JSON.*` commands, which have parsed their argument with
74/// [`Builder::json`](crate::Builder::json) by this point, and the ones that
75/// compute rather than parse, like `JSON.NUMINCRBY`, never had text at all.
76#[derive(Debug, Clone, Copy)]
77pub enum Edit<'a> {
78    /// Put these bytes in place of the value that is here.
79    Set(&'a [u8]),
80    /// Take the value that is here out of whatever holds it.
81    ///
82    /// Removing the root is refused, because a document with no value in it is
83    /// not a document. `JSON.DEL $` deletes the key, which is the keyspace's
84    /// business and not this file's.
85    Remove,
86    /// Put a member under this key into the object that is here.
87    ///
88    /// The key replaces the one already there if there is one, so this is both
89    /// halves of what `JSON.SET` does at a path whose last step is a name.
90    Put(&'a [u8], &'a [u8]),
91    /// Replace `take` elements of the array that is here, from `at`, with `put`.
92    ///
93    /// `at` and `take` are clamped to the array, so an append is `at` at or past
94    /// the end with `take` of zero, and a trim is `at` of zero with `take` of
95    /// the whole length and the kept elements in `put`.
96    Splice {
97        /// Where the replaced run starts.
98        at: usize,
99        /// How many elements it covers.
100        take: usize,
101        /// What goes in their place.
102        put: &'a [&'a [u8]],
103    },
104}
105
106/// Apply every edit in `at` to `root` and answer the document that results.
107///
108/// The offsets are into `root` and come from [`Value::offset_in`]. They may be
109/// in any order. Two edits at the same offset is a caller bug and the first one
110/// wins, and an edit inside a value that another edit replaces or removes is
111/// dropped, for the reason in the module doc.
112pub fn edit(root: &Value<'_>, at: &[(usize, Edit<'_>)]) -> Result<Vec<u8>> {
113    if at
114        .iter()
115        .any(|(off, e)| *off == 0 && matches!(e, Edit::Remove))
116    {
117        return Err(no_root());
118    }
119    let mut done = vec![false; at.len()];
120    let mut b = Builder::new();
121    write(root, root, at, &mut done, &mut b)?;
122    if done.contains(&false) {
123        return Err(stray());
124    }
125    Ok(b.finish()?.to_vec())
126}
127
128/// Write `v` into `b`, with whatever the edits say about it and about the
129/// values inside it.
130fn write(
131    root: &Value<'_>,
132    v: &Value<'_>,
133    at: &[(usize, Edit<'_>)],
134    done: &mut [bool],
135    b: &mut Builder,
136) -> Result<()> {
137    let off = v.offset_in(root).ok_or_else(stray)?;
138    let Some(what) = find(at, off) else {
139        if !inside(root, v, at)? {
140            return b.embed(v);
141        }
142        return match v.kind() {
143            Kind::Object => object(root, v, at, done, b),
144            Kind::Array => array(root, v, at, done, b),
145            // A scalar has nothing inside it, so an offset that landed in the
146            // middle of one is not the start of any value in this document.
147            _ => Err(stray()),
148        };
149    };
150    // Everything this value holds goes with it, whichever of the four this is,
151    // so nothing below this point is looked at again and the edits down there
152    // are accounted for here.
153    mark(at, done, off, off + v.encoded_len().ok_or_else(unreadable)?);
154    match what {
155        Edit::Set(bytes) => {
156            let new = Value::new(bytes)
157                .ok_or_else(|| Error::new(Code::Invalid, "the value written is not readable"))?;
158            b.embed(&new)
159        }
160        // Handled by whoever holds this value, which skips it rather than
161        // asking for it to be written. Reaching it here means it is the root,
162        // and that is refused above.
163        Edit::Remove => Err(no_root()),
164        Edit::Put(key, value) => put(root, v, key, value, at, done, b),
165        Edit::Splice {
166            at: from,
167            take,
168            put,
169        } => splice(root, v, Run { from, take, put }, at, done, b),
170    }
171}
172
173/// Rebuild an object, following the edits inside it.
174fn object(
175    root: &Value<'_>,
176    v: &Value<'_>,
177    at: &[(usize, Edit<'_>)],
178    done: &mut [bool],
179    b: &mut Builder,
180) -> Result<()> {
181    let interned = v.is_interned();
182    if interned {
183        b.begin_object_interned()?;
184    } else {
185        b.begin_object()?;
186    }
187    for i in 0..v.len() {
188        let child = v.at(i).ok_or_else(unreadable)?;
189        if skipped(root, &child, at, done)? {
190            continue;
191        }
192        if interned {
193            b.key_id(v.key_id_at(i).ok_or_else(unreadable)?)?;
194        } else {
195            b.key(v.key_at(i).ok_or_else(unreadable)?)?;
196        }
197        write(root, &child, at, done, b)?;
198    }
199    b.end_object()
200}
201
202/// Rebuild an array, following the edits inside it.
203fn array(
204    root: &Value<'_>,
205    v: &Value<'_>,
206    at: &[(usize, Edit<'_>)],
207    done: &mut [bool],
208    b: &mut Builder,
209) -> Result<()> {
210    b.begin_array()?;
211    for i in 0..v.len() {
212        let child = v.at(i).ok_or_else(unreadable)?;
213        if skipped(root, &child, at, done)? {
214            continue;
215        }
216        write(root, &child, at, done, b)?;
217    }
218    b.end_array()
219}
220
221/// Rebuild an object with one more member in it.
222///
223/// The members already there are written through [`write`] rather than embedded,
224/// because none of them is going away and an edit inside one of them still has
225/// to happen.
226fn put(
227    root: &Value<'_>,
228    v: &Value<'_>,
229    key: &[u8],
230    value: &[u8],
231    at: &[(usize, Edit<'_>)],
232    done: &mut [bool],
233    b: &mut Builder,
234) -> Result<()> {
235    if v.kind() != Kind::Object {
236        return Err(Error::new(
237            Code::Invalid,
238            "a key can only be put into an object",
239        ));
240    }
241    if v.is_interned() {
242        return Err(Error::new(
243            Code::Invalid,
244            "an object whose keys are interned needs the collection's key table to take a new key",
245        ));
246    }
247    let new = Value::new(value)
248        .ok_or_else(|| Error::new(Code::Invalid, "the value put is not readable"))?;
249    b.begin_object()?;
250    for i in 0..v.len() {
251        let child = v.at(i).ok_or_else(unreadable)?;
252        if skipped(root, &child, at, done)? {
253            continue;
254        }
255        b.key(v.key_at(i).ok_or_else(unreadable)?)?;
256        write(root, &child, at, done, b)?;
257    }
258    // Last, so that a key already in the object is the one that loses. The
259    // builder keeps the last of a repeated key, which is what every JSON parser
260    // does and what `JSON.SET` on a field that is already there has to do.
261    b.key(key)?;
262    b.embed(&new)?;
263    b.end_object()
264}
265
266/// A run of an array being replaced, which is one [`Edit::Splice`] unpacked.
267struct Run<'a> {
268    /// Where the replaced run starts.
269    from: usize,
270    /// How many elements it covers.
271    take: usize,
272    /// What goes in their place.
273    put: &'a [&'a [u8]],
274}
275
276/// Rebuild an array with a run of it replaced.
277///
278/// The elements outside the run are written through [`write`], for the reason
279/// [`put`] gives. The ones inside it are the ones going away, so an edit in
280/// there is dropped, which the `mark` in [`write`] already accounted for.
281fn splice(
282    root: &Value<'_>,
283    v: &Value<'_>,
284    run: Run<'_>,
285    at: &[(usize, Edit<'_>)],
286    done: &mut [bool],
287    b: &mut Builder,
288) -> Result<()> {
289    if v.kind() != Kind::Array {
290        return Err(Error::new(
291            Code::Invalid,
292            "elements can only be spliced into an array",
293        ));
294    }
295    let n = v.len();
296    let from = run.from.min(n);
297    let end = from.saturating_add(run.take).min(n);
298    b.begin_array()?;
299    let kept = |i: usize, b: &mut Builder, done: &mut [bool]| -> Result<()> {
300        let child = v.at(i).ok_or_else(unreadable)?;
301        if skipped(root, &child, at, done)? {
302            return Ok(());
303        }
304        write(root, &child, at, done, b)
305    };
306    for i in 0..from {
307        kept(i, b, done)?;
308    }
309    for bytes in run.put {
310        let new = Value::new(bytes)
311            .ok_or_else(|| Error::new(Code::Invalid, "an element written is not readable"))?;
312        b.embed(&new)?;
313    }
314    for i in end..n {
315        kept(i, b, done)?;
316    }
317    b.end_array()
318}
319
320/// The edit at exactly this offset, if there is one.
321///
322/// A linear scan, because the list is the matches of one path and the walk only
323/// reaches values that hold an edit or are one, so the two lengths multiply over
324/// a small number rather than over the document.
325fn find<'e>(at: &[(usize, Edit<'e>)], off: usize) -> Option<Edit<'e>> {
326    at.iter().find(|(o, _)| *o == off).map(|(_, e)| *e)
327}
328
329/// Account for the edit at `off` and for every edit inside the value there.
330fn mark(at: &[(usize, Edit<'_>)], done: &mut [bool], off: usize, end: usize) {
331    for (i, (o, _)) in at.iter().enumerate() {
332        if *o == off || (*o > off && *o < end) {
333            done[i] = true;
334        }
335    }
336}
337
338/// Whether any edit lands strictly inside `v`.
339fn inside(root: &Value<'_>, v: &Value<'_>, at: &[(usize, Edit<'_>)]) -> Result<bool> {
340    let off = v.offset_in(root).ok_or_else(stray)?;
341    let end = off + v.encoded_len().ok_or_else(unreadable)?;
342    Ok(at.iter().any(|(o, _)| *o > off && *o < end))
343}
344
345/// Whether this child is being taken out of its container.
346fn skipped(
347    root: &Value<'_>,
348    child: &Value<'_>,
349    at: &[(usize, Edit<'_>)],
350    done: &mut [bool],
351) -> Result<bool> {
352    let off = child.offset_in(root).ok_or_else(stray)?;
353    if !matches!(find(at, off), Some(Edit::Remove)) {
354        return Ok(false);
355    }
356    mark(
357        at,
358        done,
359        off,
360        off + child.encoded_len().ok_or_else(unreadable)?,
361    );
362    Ok(true)
363}
364
365fn no_root() -> Error {
366    Error::new(
367        Code::Invalid,
368        "the whole document cannot be removed, only the key it is under",
369    )
370}
371
372fn stray() -> Error {
373    Error::new(
374        Code::Invalid,
375        "an offset that no value in this document begins at",
376    )
377}
378
379fn unreadable() -> Error {
380    Error::new(Code::Corrupt, "the document being edited is not readable")
381}
382
383#[cfg(test)]
384mod tests {
385    use super::*;
386    use crate::Path;
387    use crate::text::from_json;
388
389    /// Every place `path` names in `doc`, as offsets into it.
390    fn hits(doc: &[u8], path: &[u8]) -> Vec<usize> {
391        let root = Value::new(doc).expect("readable");
392        let mut found = Vec::new();
393        Path::parse(path)
394            .expect("the path parses")
395            .select(&root, &mut found);
396        found
397            .iter()
398            .map(|v| v.offset_in(&root).expect("from this document"))
399            .collect()
400    }
401
402    /// `text` with `what` done at every place `path` names, back as JSON text.
403    fn changed(text: &str, path: &[u8], what: Edit<'_>) -> String {
404        let doc = from_json(text.as_bytes()).expect("the text parses");
405        let root = Value::new(&doc).expect("readable");
406        let at: Vec<_> = hits(&doc, path)
407            .into_iter()
408            .map(|off| (off, what))
409            .collect();
410        let after = edit(&root, &at).expect("the edit applies");
411        let out = Value::new(&after).expect("readable");
412        assert!(out.validate(), "the document that came out is whole");
413        String::from_utf8(out.to_json().expect("writable")).expect("UTF-8")
414    }
415
416    #[test]
417    fn a_value_is_replaced_wherever_the_path_names_it() {
418        let nine = from_json(b"9").expect("parses");
419        assert_eq!(
420            changed(r#"{"a":{"n":1},"b":{"n":2}}"#, b"$..n", Edit::Set(&nine)),
421            r#"{"a":{"n":9},"b":{"n":9}}"#
422        );
423        assert_eq!(
424            changed(r#"{"a":[1,2,3]}"#, b"$.a[*]", Edit::Set(&nine)),
425            r#"{"a":[9,9,9]}"#
426        );
427        assert_eq!(changed("[1,2]", b"$", Edit::Set(&nine)), "9");
428    }
429
430    #[test]
431    fn a_value_that_is_removed_leaves_no_hole() {
432        assert_eq!(
433            changed(r#"{"a":1,"bb":2,"cc":3}"#, b"$.bb", Edit::Remove),
434            r#"{"a":1,"cc":3}"#
435        );
436        assert_eq!(changed("[1,2,3]", b"$[1]", Edit::Remove), "[1,3]");
437        assert_eq!(changed("[1,2,3]", b"$[*]", Edit::Remove), "[]");
438        assert_eq!(
439            changed(r#"{"a":{"n":1},"b":{"n":2}}"#, b"$..n", Edit::Remove),
440            r#"{"a":{},"b":{}}"#
441        );
442    }
443
444    #[test]
445    fn a_removal_inside_a_removal_is_dropped_rather_than_refused() {
446        // `$..a` matches the outer object and the number inside it. Taking the
447        // outer one away already took the inner one, and the inner edit has
448        // nothing left to change.
449        assert_eq!(
450            changed(r#"{"a":{"a":1},"b":2}"#, b"$..a", Edit::Remove),
451            r#"{"b":2}"#
452        );
453    }
454
455    #[test]
456    fn the_whole_document_cannot_be_removed() {
457        let doc = from_json(br#"{"a":1}"#).expect("parses");
458        let root = Value::new(&doc).expect("readable");
459        let why = edit(&root, &[(0, Edit::Remove)]).unwrap_err();
460        assert!(
461            why.message().contains("only the key it is under"),
462            "{}",
463            why.message()
464        );
465    }
466
467    #[test]
468    fn a_key_put_into_an_object_lands_in_key_order() {
469        let one = from_json(b"1").expect("parses");
470        assert_eq!(
471            changed(r#"{"aa":1,"cc":3}"#, b"$", Edit::Put(b"bb", &one)),
472            r#"{"aa":1,"bb":1,"cc":3}"#
473        );
474        // A key already there is replaced and not repeated.
475        assert_eq!(
476            changed(r#"{"aa":1,"cc":3}"#, b"$", Edit::Put(b"cc", &one)),
477            r#"{"aa":1,"cc":1}"#
478        );
479        // Under every object a wildcard names, which is what `JSON.SET` with a
480        // path like `$.*.tag` has to do.
481        assert_eq!(
482            changed(
483                r#"{"a":{"x":1},"b":{"x":2}}"#,
484                b"$.*",
485                Edit::Put(b"n", &one)
486            ),
487            r#"{"a":{"n":1,"x":1},"b":{"n":1,"x":2}}"#
488        );
489    }
490
491    #[test]
492    fn a_key_cannot_be_put_into_something_that_is_not_an_object() {
493        let one = from_json(b"1").expect("parses");
494        let doc = from_json(b"[1,2]").expect("parses");
495        let root = Value::new(&doc).expect("readable");
496        let why = edit(&root, &[(0, Edit::Put(b"a", &one))]).unwrap_err();
497        assert!(
498            why.message().contains("only be put into an object"),
499            "{}",
500            why.message()
501        );
502    }
503
504    #[test]
505    fn a_splice_covers_appending_inserting_popping_and_trimming() {
506        let seven = from_json(b"7").expect("parses");
507        let eight = from_json(b"8").expect("parses");
508        let put: &[&[u8]] = &[&seven, &eight];
509
510        let append = Edit::Splice {
511            at: usize::MAX,
512            take: 0,
513            put,
514        };
515        assert_eq!(changed("[1,2]", b"$", append), "[1,2,7,8]");
516
517        let insert = Edit::Splice {
518            at: 1,
519            take: 0,
520            put,
521        };
522        assert_eq!(changed("[1,2]", b"$", insert), "[1,7,8,2]");
523
524        let pop = Edit::Splice {
525            at: 2,
526            take: 1,
527            put: &[],
528        };
529        assert_eq!(changed("[1,2,3]", b"$", pop), "[1,2]");
530
531        // A trim is the kept run put back over the whole array.
532        let kept: &[&[u8]] = &[&seven];
533        let trim = Edit::Splice {
534            at: 0,
535            take: usize::MAX,
536            put: kept,
537        };
538        assert_eq!(changed("[1,2,3]", b"$", trim), "[7]");
539
540        // Every array a descent names, all at once.
541        let each = Edit::Splice {
542            at: 0,
543            take: 0,
544            put: kept,
545        };
546        assert_eq!(
547            changed(r#"{"a":[1],"b":[2]}"#, b"$..a", each),
548            r#"{"a":[7,1],"b":[2]}"#
549        );
550    }
551
552    #[test]
553    fn elements_cannot_be_spliced_into_something_that_is_not_an_array() {
554        let doc = from_json(br#"{"a":1}"#).expect("parses");
555        let root = Value::new(&doc).expect("readable");
556        let what = Edit::Splice {
557            at: 0,
558            take: 0,
559            put: &[],
560        };
561        let why = edit(&root, &[(0, what)]).unwrap_err();
562        assert!(
563            why.message().contains("only be spliced into an array"),
564            "{}",
565            why.message()
566        );
567    }
568
569    #[test]
570    fn everything_the_edit_did_not_name_comes_back_byte_identical() {
571        let text = br#"{"keep":[{"deep":[1,2,{"here":true}]},"text",null],"n":1.5,"go":0}"#;
572        let doc = from_json(text).expect("parses");
573        let root = Value::new(&doc).expect("readable");
574        let at: Vec<_> = hits(&doc, b"$.go")
575            .into_iter()
576            .map(|off| (off, Edit::Remove))
577            .collect();
578        let after = edit(&root, &at).expect("applies");
579
580        let out = Value::new(&after).expect("readable");
581        assert_eq!(
582            out.get(b"keep").expect("still there").as_bytes(),
583            root.get(b"keep").expect("was there").as_bytes(),
584            "the part nobody touched is the same bytes"
585        );
586        assert!(out.get(b"go").is_none());
587        assert_eq!(out.len(), 2);
588    }
589
590    #[test]
591    fn a_splice_or_a_put_still_carries_the_edits_inside_what_it_keeps() {
592        let one = from_json(b"1").expect("parses");
593        let put: &[&[u8]] = &[&one];
594        // `$..a` names the outer array and the inner one it holds, and an
595        // append with that path has to reach both.
596        assert_eq!(
597            changed(
598                r#"{"a":[{"a":[7]}]}"#,
599                b"$..a",
600                Edit::Splice {
601                    at: usize::MAX,
602                    take: 0,
603                    put,
604                }
605            ),
606            r#"{"a":[{"a":[7,1]},1]}"#
607        );
608        // The same for a member going into an object that also holds one.
609        assert_eq!(
610            changed(
611                r#"{"o":{"o":{}}}"#,
612                b"$..o",
613                Edit::Put(b"n", one.as_slice())
614            ),
615            r#"{"o":{"n":1,"o":{"n":1}}}"#
616        );
617        // What the splice takes out is going away, so an edit in there is
618        // dropped the way one inside a removed value is.
619        assert_eq!(
620            changed(
621                r#"{"a":[{"a":[7]}]}"#,
622                b"$..a",
623                Edit::Splice {
624                    at: 0,
625                    take: usize::MAX,
626                    put: &[],
627                }
628            ),
629            r#"{"a":[]}"#
630        );
631    }
632
633    #[test]
634    fn no_edits_at_all_is_the_document_it_was_given() {
635        let doc = from_json(br#"{"a":[1,{"b":"c"}],"d":null}"#).expect("parses");
636        let root = Value::new(&doc).expect("readable");
637        assert_eq!(edit(&root, &[]).expect("applies"), doc);
638    }
639
640    #[test]
641    fn an_offset_that_is_not_a_value_says_so() {
642        let doc = from_json(br#"{"a":1}"#).expect("parses");
643        let root = Value::new(&doc).expect("readable");
644        let one = from_json(b"1").expect("parses");
645        // Two bytes into the root's own header, which is inside the document
646        // and is not the start of anything in it.
647        let why = edit(&root, &[(2, Edit::Set(&one))]).unwrap_err();
648        assert!(
649            why.message()
650                .contains("no value in this document begins at"),
651            "{}",
652            why.message()
653        );
654        // Past the end of it, which is the same mistake from the other side.
655        let why = edit(&root, &[(doc.len() + 8, Edit::Set(&one))]).unwrap_err();
656        assert!(
657            why.message()
658                .contains("no value in this document begins at"),
659            "{}",
660            why.message()
661        );
662    }
663}