Skip to main content

dataflow_rs/function/
overlay.rs

1//! A host call's overlay: the writes one call makes, kept apart from the run
2//! until the call settles.
3//!
4//! It is the list of the call's writes as it made them: each a path
5//! and the value written there, or a removal. [`Ctx`](super::Ctx) uses it
6//! through the operations below, so a change of structure touches this file
7//! only.
8//!
9//! - A read scans the list from the newest write. The newest write at or
10//!   above the path decides it; one below it, or one that may have moved
11//!   it, sends the read to the call's own root: the base with the writes
12//!   spliced on in order.
13//! - An evaluation reads that root.
14//! - The settle replays the writes through the run context in order, each at
15//!   its own path, against the context as it stands then. So overlays
16//!   planned over one base compose, as v3's fold replays each element's
17//!   `Change`s, and a write lands on the writes settled before it.
18//!
19//! Two segments can name one place without being equal (`1`, `01` and `+1`
20//! one element; `1` and `#1` one key), an element's removal moves the
21//! elements after it, and a write past an array's end pads it. A read the
22//! list cannot answer for certain is sent to the spliced root, which can.
23
24use crate::expr::arena::with_arena;
25use crate::expr::path;
26use crate::expr::rules::{self, Shape, Step};
27use crate::expr::{Path, Seg};
28use crate::state::splice;
29use crate::state::{Lookup, RunContext};
30use bumpalo::Bump;
31use datavalue::{DataValue, OwnedDataValue};
32
33/// The writes of one call, in the order they were made.
34#[derive(Clone, Debug, Default)]
35pub(crate) struct Overlay(Vec<(Path, Write)>);
36
37/// One write a call made, at the path it named: what
38/// [`testing::Called::writes`](super::testing::Called::writes) lists, in the
39/// order the call made them.
40#[derive(Clone, Debug, PartialEq)]
41#[non_exhaustive]
42pub enum Write {
43    /// A value written, which the call's own context took: the call reads it
44    /// there from then on.
45    Set(OwnedDataValue),
46    /// A value written where the call's own context could not take it, below
47    /// a scalar. It changes nothing the call reads, and the settle still
48    /// replays it at its path, since the message may take it by then, as v3
49    /// replays every `Change`.
50    Blocked(OwnedDataValue),
51    /// A value the call saw, removed.
52    Removed,
53}
54
55impl Overlay {
56    /// Whether the call has written nothing.
57    pub(crate) fn is_empty(&self) -> bool {
58        self.0.is_empty()
59    }
60
61    /// Writes `value` at `path` over `base`, the root the call reads beneath
62    /// its own writes. Whether the call's own root takes it, by `Path::set`'s
63    /// rules, is settled here, against `base` and the writes before it.
64    pub(crate) fn set(&mut self, base: DataValue<'_>, path: &Path, value: OwnedDataValue) {
65        let lands = self.lands(base, path.segs());
66        self.push(path, value, lands);
67    }
68
69    /// Writes `value` at `path` where the caller has settled whether the
70    /// call's own root takes it: `Ctx`'s first write learns it in the walk
71    /// that reads the old value.
72    pub(crate) fn push(&mut self, path: &Path, value: OwnedDataValue, lands: bool) {
73        let write = if lands {
74            Write::Set(value)
75        } else {
76            Write::Blocked(value)
77        };
78        self.0.push((path.clone(), write));
79    }
80
81    /// Removes `path`, where the call sees a value.
82    pub(crate) fn remove(&mut self, path: &Path) {
83        self.0.push((path.clone(), Write::Removed));
84    }
85
86    /// What a read of `path` finds here: `Owned` written here (`None` when
87    /// removed), `Dirty` when written below it or moved, so the read
88    /// splices, or `Clean` when no write reaches it, and the base answers.
89    pub(crate) fn lookup(&self, path: &Path) -> Lookup<'_> {
90        let p = path.segs();
91        for (at, write) in self.0.iter().rev() {
92            let e = at.segs();
93            let set = match write {
94                Write::Set(value) => Some(value),
95                Write::Removed => None,
96                Write::Blocked(_) => continue,
97            };
98            match relation(e, p) {
99                Rel::Apart => {}
100                Rel::Siblings { at, e: x, p: y } if !moves(e.len(), set.is_some(), at, x, y) => {}
101                Rel::Above => {
102                    return match set {
103                        Some(value) => Lookup::Owned(path::get(value, &p[e.len()..])),
104                        None if may_shift(e) => Lookup::Dirty,
105                        None => Lookup::Owned(None),
106                    };
107                }
108                Rel::Below | Rel::Siblings { .. } | Rel::Unsure => return Lookup::Dirty,
109            }
110        }
111        Lookup::Clean
112    }
113
114    /// `root` with every write spliced on, in order, in `arena`. The values
115    /// are viewed, not copied.
116    pub(crate) fn splice_onto<'a>(&'a self, root: DataValue<'a>, arena: &'a Bump) -> DataValue<'a> {
117        self.0.iter().fold(root, |root, (path, write)| {
118            let spliced = match write {
119                Write::Set(value) => splice::set(arena, root, path.segs(), value.view_in(arena)),
120                Write::Removed => splice::remove(arena, root, path.segs()),
121                Write::Blocked(_) => return root,
122            };
123            debug_assert!(
124                spliced.is_some(),
125                "a write lands on the root it was made on"
126            );
127            spliced.unwrap_or(root)
128        })
129    }
130
131    /// The writes, in the order the call made them.
132    pub(crate) fn into_writes(self) -> Vec<(Path, Write)> {
133        self.0
134    }
135
136    /// Replays every write through `rc`, in the order the call made them,
137    /// each at its own path, against the context as it stands: one that no
138    /// longer lands writes nothing. A write is not recorded again: the call
139    /// recorded it.
140    pub(crate) fn apply(self, rc: &mut RunContext<'_, '_>) {
141        for (path, write) in self.0 {
142            match write {
143                Write::Set(value) | Write::Blocked(value) => {
144                    rc.set_segs(path.segs(), value, None);
145                }
146                Write::Removed => {
147                    rc.remove_segs(path.segs());
148                }
149            }
150        }
151    }
152
153    /// Whether a write at `segs` lands on the call's own root, by
154    /// [`rules::step`] at every segment, over what the call's own writes
155    /// left there: each container on the way is an object, or an array the
156    /// next segment indexes, and a missing one or a `null` element is
157    /// created, with everything below it.
158    fn lands(&self, base: DataValue<'_>, segs: &[Seg]) -> bool {
159        if self.is_empty() {
160            return splice::resolve(base, segs).is_some();
161        }
162        // Every root is an object, and so holds `segs[1]`.
163        let mut holder = Shape::Object;
164        for i in 1..segs.len() {
165            let last = i + 1 == segs.len();
166            // The last segment's child is replaced whatever it holds, so
167            // only its holder decides.
168            let child = if last {
169                None
170            } else {
171                match self.held_at(base, &segs[..=i]) {
172                    Some(held) => held,
173                    None => {
174                        return with_arena(|arena| {
175                            splice::resolve(self.splice_onto(base, arena), segs).is_some()
176                        });
177                    }
178                }
179            };
180            match rules::step(holder, &segs[i], child, last) {
181                Step::Block => return false,
182                Step::Create => return true,
183                Step::Into => holder = child.unwrap_or(Shape::Null),
184            }
185        }
186        true
187    }
188
189    /// What the call's own root holds at `q` (`Some(None)` when nothing);
190    /// `None` when only the spliced root can tell.
191    fn held_at(&self, base: DataValue<'_>, q: &[Seg]) -> Option<Option<Shape>> {
192        // The first segment below `q` of the earliest write since the one
193        // that decides `q` that landed below it: what created `q`, if it
194        // was missing or a `null` element.
195        let mut created = None;
196        for (at, write) in self.0.iter().rev() {
197            let e = at.segs();
198            let set = match write {
199                Write::Set(value) => Some(value),
200                Write::Removed => None,
201                Write::Blocked(_) => continue,
202            };
203            match relation(e, q) {
204                Rel::Apart => {}
205                Rel::Siblings { at, e: x, p: y } if !moves(e.len(), set.is_some(), at, x, y) => {}
206                Rel::Below => {
207                    if set.is_some() {
208                        created = Some(&e[q.len()]);
209                    }
210                }
211                Rel::Above => {
212                    let held = match set {
213                        Some(value) => path::get(value, &q[e.len()..]).map(Shape::of_owned),
214                        None if may_shift(e) => return None,
215                        None => None,
216                    };
217                    return Some(or_created(held, created));
218                }
219                Rel::Siblings { .. } | Rel::Unsure => return None,
220            }
221        }
222        Some(or_created(
223            splice::get(base, q).map(|v| Shape::of_view(&v)),
224            created,
225        ))
226    }
227}
228
229/// What a path holds once a write landed below it: a missing container, or
230/// a `null` element, is created as the path rules create it
231/// ([`rules::container_for`] the segment below). Only a `null` element takes
232/// a write below it, so a `null` that stays is one under an object.
233fn or_created(held: Option<Shape>, below: Option<&Seg>) -> Option<Shape> {
234    match (held, below) {
235        (None | Some(Shape::Null), Some(next)) => Some(rules::container_for(next)),
236        (held, _) => held,
237    }
238}
239
240/// How a write's path `e` stands to a path `p` the overlay is asked about.
241#[derive(Clone, Copy, Debug, PartialEq, Eq)]
242enum Rel {
243    /// `e` names `p` or a path above it: what `e` wrote decides `p`.
244    Above,
245    /// `e` names a path below `p`.
246    Below,
247    /// The two part at a segment where they name different places.
248    Apart,
249    /// The two part at segment `at`, where both name an array element:
250    /// `e`'s and `p`'s.
251    Siblings { at: usize, e: usize, p: usize },
252    /// The two part at a segment that may name one place spelled two ways:
253    /// one element (`1`, `01`), or one key, one spelling an index (`1`,
254    /// `#1`).
255    Unsure,
256}
257
258fn relation(e: &[Seg], p: &[Seg]) -> Rel {
259    for (i, (a, b)) in e.iter().zip(p).enumerate() {
260        let one_key = a.hash == b.hash && a.key == b.key;
261        if one_key && a.index == b.index {
262            continue;
263        }
264        return match (a.index, b.index) {
265            (Some(x), Some(y)) if x != y => Rel::Siblings { at: i, e: x, p: y },
266            (Some(_), Some(_)) => Rel::Unsure,
267            _ if one_key => Rel::Unsure,
268            _ => Rel::Apart,
269        };
270    }
271    if e.len() <= p.len() {
272        Rel::Above
273    } else {
274        Rel::Below
275    }
276}
277
278/// Whether a write naming element `x`, at segment `at` of a path `e_len`
279/// segments long, may change what element `y` holds: a value written past
280/// `y` may pad the array up to it, and an element removed before `y` moves
281/// the next one into its place.
282fn moves(e_len: usize, set: bool, at: usize, x: usize, y: usize) -> bool {
283    if set { x > y } else { e_len == at + 1 && x < y }
284}
285
286/// Whether a removal at `e` may have been of an array element, so the next
287/// element moved into its place.
288fn may_shift(e: &[Seg]) -> bool {
289    e.last().is_some_and(|seg| seg.index.is_some())
290}
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295    use crate::state::tests::random::{self, Rng};
296    use crate::state::{RunContext, Tuning};
297
298    /// Applies one write or removal to the model and the overlay, as `Ctx`
299    /// would make it: a root takes only an object and is never removed, and
300    /// removing nothing is nothing.
301    fn step(rng: &mut Rng, overlay: &mut Overlay, base: DataValue<'_>, model: &mut OwnedDataValue) {
302        let Ok(path) = random::rooted_path(rng, model).parse::<Path>() else {
303            return;
304        };
305        if rng.chance(1, 3) {
306            if path.is_root() || path.get(model).is_none() {
307                return;
308            }
309            path::remove(model, path.segs());
310            overlay.remove(&path);
311        } else {
312            let value = random::value(rng);
313            if path.check_set(&value).is_err() {
314                return;
315            }
316            path.assign(model, value.clone());
317            overlay.set(base, &path, value);
318        }
319    }
320
321    #[test]
322    fn the_overlay_is_its_writes_applied_in_order() {
323        let mut rng = Rng::new(0x5eed_0f0e_71a7);
324        for case in 0..1500 {
325            let owned = random::rooted_tree(&mut rng, 3);
326            let arena = Bump::new();
327            let base = owned.view_in(&arena);
328            let mut overlay = Overlay::default();
329            let mut model = owned.clone();
330            for at in 0..1 + rng.below(6) {
331                step(&mut rng, &mut overlay, base, &mut model);
332                let here = Bump::new();
333                let root = overlay.splice_onto(base, &here);
334                let want = random::exact(&model);
335                assert_eq!(
336                    random::exact(&root.to_owned()),
337                    want,
338                    "case {case} step {at}: the spliced root"
339                );
340                // Every read agrees with the model, whichever part answers.
341                for _ in 0..4 {
342                    let Ok(read) = random::rooted_path(&mut rng, &model).parse::<Path>() else {
343                        continue;
344                    };
345                    let got = match overlay.lookup(&read) {
346                        Lookup::Clean => read.get(&owned).cloned(),
347                        Lookup::Owned(v) => v.cloned(),
348                        Lookup::Dirty => splice::get(root, read.segs()).map(|v| v.to_owned()),
349                    };
350                    assert_eq!(
351                        got.as_ref().map(random::exact),
352                        read.get(&model).map(random::exact),
353                        "case {case} step {at}: a read of {read}"
354                    );
355                }
356            }
357            // Applied through a run context, with or without its view.
358            let mut slot = owned.clone();
359            let mut rc = RunContext::new(&mut slot, Tuning::default());
360            if rng.chance(1, 2) {
361                rc.read(|_, _| ());
362            }
363            overlay.apply(&mut rc);
364            rc.finish();
365            assert_eq!(
366                random::exact(&slot),
367                random::exact(&model),
368                "case {case}: applied"
369            );
370        }
371    }
372
373    /// One write or removal a call makes over `own`, its own root, by
374    /// `Ctx`'s rules: a root takes only an object and is never removed, and
375    /// a removal is made only where the call sees a value. `None` when the
376    /// draw is none of these.
377    fn draw(rng: &mut Rng, own: &OwnedDataValue) -> Option<(Path, Option<OwnedDataValue>)> {
378        let path = random::rooted_path(rng, own).parse::<Path>().ok()?;
379        if rng.chance(1, 3) {
380            (!path.is_root() && path.get(own).is_some()).then_some((path, None))
381        } else {
382            let value = random::value(rng);
383            path.check_set(&value)
384                .is_ok()
385                .then_some((path, Some(value)))
386        }
387    }
388
389    /// `path` written with `value`, or removed, in an owned context: what
390    /// `Ctx::set` and `Ctx::remove` mean.
391    fn write(context: &mut OwnedDataValue, path: &Path, value: Option<&OwnedDataValue>) {
392        match value {
393            Some(value) => {
394                path.assign(context, value.clone());
395            }
396            None => {
397                path::remove(context, path.segs());
398            }
399        }
400    }
401
402    /// Two or three calls each plan their writes over one base, as a
403    /// fan-out's elements do, and their overlays are applied in call order
404    /// through one run context. That must equal every call's writes applied
405    /// in that order to one context: each write replayed at its own path,
406    /// against the context as it stands, as v3's fold replays each
407    /// element's `Change`s.
408    #[test]
409    fn overlays_planned_over_one_base_compose_in_order() {
410        let mut rng = Rng::new(0xc0_4405_e0f1);
411        for case in 0..1500 {
412            let owned = random::rooted_tree(&mut rng, 3);
413            let arena = Bump::new();
414            let base = owned.view_in(&arena);
415            let mut sequential = owned.clone();
416            let mut overlays = Vec::new();
417            for _ in 0..2 + rng.below(2) {
418                let mut overlay = Overlay::default();
419                let mut own = owned.clone();
420                for _ in 0..1 + rng.below(5) {
421                    let Some((path, value)) = draw(&mut rng, &own) else {
422                        continue;
423                    };
424                    write(&mut own, &path, value.as_ref());
425                    write(&mut sequential, &path, value.as_ref());
426                    match value {
427                        Some(value) => overlay.set(base, &path, value),
428                        None => overlay.remove(&path),
429                    }
430                }
431                overlays.push(overlay);
432            }
433            let mut slot = owned.clone();
434            let mut rc = RunContext::new(&mut slot, Tuning::default());
435            if rng.chance(1, 2) {
436                rc.read(|_, _| ());
437            }
438            for overlay in overlays {
439                overlay.apply(&mut rc);
440            }
441            rc.finish();
442            let (got, want) = (random::exact(&slot), random::exact(&sequential));
443            assert!(got == want, "case {case}: {}", random::diff(&got, &want));
444        }
445    }
446
447    fn parse(dotted: &str) -> Path {
448        dotted.parse().unwrap()
449    }
450
451    fn dv(v: serde_json::Value) -> OwnedDataValue {
452        OwnedDataValue::from(&v)
453    }
454
455    /// The overlay's writes over `base`, spliced, as wire text: key order
456    /// is what these tests check.
457    fn spliced(overlay: &Overlay, base: DataValue<'_>) -> String {
458        let arena = Bump::new();
459        random::exact(&overlay.splice_onto(base, &arena).to_owned())
460    }
461
462    #[test]
463    fn a_key_removed_and_written_again_moves_to_its_objects_end() {
464        let owned = dv(serde_json::json!({"data": {"a": 1, "b": 2, "c": 3}}));
465        let arena = Bump::new();
466        let base = owned.view_in(&arena);
467        let mut overlay = Overlay::default();
468        overlay.remove(&parse("data.a"));
469        overlay.set(base, &parse("data.a"), dv(serde_json::json!(4)));
470        assert_eq!(spliced(&overlay, base), r#"{"data":{"b":2,"c":3,"a":4}}"#);
471    }
472
473    #[test]
474    fn a_new_key_removed_and_written_again_follows_a_later_new_key() {
475        let owned = dv(serde_json::json!({"data": {"a": 1}}));
476        let arena = Bump::new();
477        let base = owned.view_in(&arena);
478        let mut overlay = Overlay::default();
479        overlay.set(base, &parse("data.n"), dv(serde_json::json!(1)));
480        overlay.set(base, &parse("data.m"), dv(serde_json::json!(2)));
481        overlay.remove(&parse("data.n"));
482        overlay.set(base, &parse("data.n"), dv(serde_json::json!(3)));
483        assert_eq!(spliced(&overlay, base), r#"{"data":{"a":1,"m":2,"n":3}}"#);
484    }
485
486    #[test]
487    fn padding_and_a_removed_element_are_read_as_written() {
488        let owned = dv(serde_json::json!({"data": {"list": [1, 2]}}));
489        let arena = Bump::new();
490        let base = owned.view_in(&arena);
491        let mut overlay = Overlay::default();
492        overlay.set(base, &parse("data.list.4"), dv(serde_json::json!(5)));
493        // The padding is not the base's to answer: it reads off the spliced
494        // root, as `null`, not as missing.
495        assert_eq!(overlay.lookup(&parse("data.list.3")), Lookup::Dirty);
496        assert_eq!(
497            spliced(&overlay, base),
498            r#"{"data":{"list":[1,2,null,null,5]}}"#
499        );
500        overlay.remove(&parse("data.list.0"));
501        assert_eq!(
502            spliced(&overlay, base),
503            r#"{"data":{"list":[2,null,null,5]}}"#
504        );
505    }
506}