beck_core/
engine.rs

1//! The incremental view engine: the thing that flows deltas through a [`crate::plan::Plan`].
2//!
3//! [`docs/03-type-and-effect-system.md`](../../../../../docs/03-type-and-effect-system.md) §3.8:
4//! "`remaining` updates by ±1 per event, never by recount." Until now that sentence described an
5//! intention. This is the machine that makes it true, and
6//! [`docs/23-incremental-views-report.md`](../../../../../docs/23-incremental-views-report.md) is the
7//! measurement.
8//!
9//! # The one hard problem, and where it is solved
10//!
11//! A Beck program's state is a *value*: `todos = durable(fold(apply_event, empty, events))` produces
12//! a whole new accumulator per event. A dataflow plan consumes *changes*. Something has to convert
13//! one into the other, and doing it by comparing the old and new accumulator entry by entry would
14//! be `O(n)` per event — which is the recount §3.8 exists to abolish, moved one level down where it
15//! is harder to see.
16//!
17//! [`crate::pmap::PMap::diff`] is the conversion, and it is `O(δ log n)` because `Map[K, V]` is a
18//! persistent tree: two versions that differ by one insert *share* every subtree the insert did not
19//! pass through, by pointer, and the diff skips a shared subtree whole. So the delta at the source
20//! costs what the delta is worth. Everything downstream of that is ordinary differential dataflow.
21//!
22//! # Correctness before speed
23//!
24//! The engine's output must be *identical* to recomputing the view — not close, identical, because
25//! the rendered page is diffed into a patch stream and replayed bit for bit (§4.8). Three things
26//! make that checkable rather than hoped for:
27//!
28//! 1. **Every operator the plan cannot decompose is a full recompute** ([`Op::Pointwise`]), so a
29//!    program the analysis does not understand is *slow*, never wrong.
30//! 2. **Order is a key, not a sort.** Each arrangement is a `BTreeMap` whose key reproduces the
31//!    order the recompute would have produced ([`crate::plan`]), so `map_values` order, `sort_by`
32//!    stability and `concat_lists` position all fall out of the key rather than out of a final pass.
33//! 3. **An error resets the engine.** A per-element function that fails leaves an arrangement
34//!    half-updated, so [`Engine::render`] discards everything and the next call rebuilds. A stale
35//!    arrangement is the one failure mode that would be invisible.
36//!
37//! `beck-cli/tests/incremental_engine.rs` is the harness: every corpus program, every event of a
38//! generated log, engine against recompute, byte for byte.
39//!
40//! # What "changed" means, and why it is never a deep comparison
41//!
42//! A pointwise operator re-runs when an input changed. Deciding that by structural equality would
43//! reintroduce the `O(n)` this module exists to remove, so `same` is a *conservative* test:
44//! scalars compare by value, collections and rendered trees by pointer. It answers "unchanged" only
45//! when it is certain, and "changed" costs a recompute that the old runtime did unconditionally.
46
47use std::collections::{BTreeMap, BTreeSet, VecDeque};
48use std::sync::atomic::{AtomicU64, Ordering};
49use std::sync::{Arc, OnceLock, RwLock};
50
51use beck_diag::Span;
52
53use crate::backend::{Backend, Callable, ExecError};
54use crate::core::Value;
55use crate::plan::{Agg, Fun, Matching, Op, OpId, Plan, Presence};
56use crate::pmap::PMap;
57use crate::split::Placed;
58
59/// What orders an entry inside an arrangement. See [`crate::plan`] for where each operator's key
60/// comes from.
61pub type Key = Arc<[Value]>;
62
63/// One entry's fate at an operator's output.
64#[derive(Clone, Debug)]
65pub struct Change {
66    pub key: Key,
67    pub old: Option<Value>,
68    pub new: Option<Value>,
69}
70
71/// An operator's output as a keyed collection — §5.3's "arrangement".
72#[derive(Clone, Debug, Default)]
73struct Arrangement {
74    entries: BTreeMap<Key, Value>,
75    /// The `Value::List` a pointwise consumer needs, built on demand and dropped when the
76    /// arrangement moves. This is the boundary between the maintained region and the rest: a
77    /// consumer that only asks for the *size* never forces it, which is exactly why `list_len` is
78    /// an operator rather than a pointwise call.
79    ///
80    /// A `OnceLock` rather than an `Option` because a *shared* arrangement is read by many
81    /// subscribers at once, through a read lock ([`SharedDataflow`]): the first one to need the
82    /// list builds it, and the rest get the same `Arc`. With an `Option` the cache would need the
83    /// write lock, which would serialise every subscriber behind the first — and the list is the
84    /// one thing worth sharing most, because building it is the `O(n)` §23.8 named.
85    listed: OnceLock<Value>,
86}
87
88impl Arrangement {
89    fn touch(&mut self) {
90        self.listed = OnceLock::new();
91    }
92
93    /// The list this arrangement stands for, and how many entries had to be copied to build it —
94    /// zero when another reader already had.
95    fn listed_value(&self) -> (Value, u64) {
96        if let Some(v) = self.listed.get() {
97            return (v.clone(), 0);
98        }
99        let listed = Value::list(self.entries.values().cloned().collect());
100        // A race loses the loser's copy and keeps the winner's; both are the same list, so which
101        // one wins is not observable. `get_or_init` would be neater and would hold a lock.
102        match self.listed.set(listed.clone()) {
103            Ok(()) => (listed, self.entries.len() as u64),
104            Err(_) => (
105                self.listed.get().cloned().unwrap_or(listed),
106                self.entries.len() as u64,
107            ),
108        }
109    }
110}
111
112#[derive(Clone, Debug)]
113enum Out {
114    Val(Value),
115    Arr(Arrangement),
116}
117
118impl Default for Out {
119    fn default() -> Self {
120        Out::Val(Value::Unit)
121    }
122}
123
124/// One operator's runtime state.
125#[derive(Default)]
126struct Cell {
127    out: Out,
128    changed: bool,
129    /// For an arrangement: what moved this tick.
130    changes: Vec<Change>,
131    /// Set when this operator threw its arrangement away and rebuilt it this tick.
132    ///
133    /// A rebuild emits *inserts only* — there is no previous arrangement left to derive removals
134    /// from — so a consumer that merely applied those inserts to its own arrangement would keep
135    /// every entry the rebuild dropped. This is how a subscriber switching sessions saw another
136    /// subscriber's rows, and the flag is the fix: a rebuild is contagious downstream.
137    rebuilt: bool,
138    /// `map_values`: the map this operator last saw, so the next one can be diffed against it.
139    seen_map: PMap<Value, Value>,
140    /// For each input that arrives as a plain list rather than as an arrangement, the copy this
141    /// operator last saw. A list-valued input has no deltas of its own, so the operator makes them.
142    shadow: Vec<BTreeMap<Key, Value>>,
143    /// `sort_by`: where each input key currently sits in the output order. `join`: the join key a
144    /// left row is waiting on. `group_by`: the group a row is in and what it contributed to it,
145    /// which is what lets the row be withdrawn without re-applying either function to a value that
146    /// has already gone.
147    positions: BTreeMap<Key, Key>,
148    /// `flatten`: how many entries each input key currently contributes, so the old ones can be
149    /// withdrawn without scanning the arrangement.
150    counts: BTreeMap<Key, usize>,
151    /// `join`: which left rows are currently waiting on each join key.
152    ///
153    /// The reverse of the index, and what makes the *right* half of the delta rule `O(δ)`. Without
154    /// it a right row that moved would have to ask every left row whether it cared, which is the
155    /// nested loop this operator exists to remove — arrived at from the other side.
156    back: BTreeMap<Value, BTreeSet<Key>>,
157    /// `join`, for [`Matching::Count`]: how many index entries each join key currently holds.
158    ///
159    /// Kept by the join rather than by the index because an operator reads its inputs' *values* and
160    /// their changes, never their private state — an index in the shared dataflow is not this
161    /// engine's cell at all ([`SharedDataflow`]). The change stream carries everything a count
162    /// needs: an entry that arrived is `+1` and one that left is `-1`, and the two are the same
163    /// arithmetic whatever operator produced them.
164    tally: BTreeMap<Value, u64>,
165    /// `group_by`: per group, a multiset of what its rows projected to.
166    ///
167    /// A count against each distinct value rather than a list of them, because two rows that
168    /// project to the same value are indistinguishable to a `min` and the multiplicity is what says
169    /// when the last of them has left. The size is the group's *distinct* values, which is at most
170    /// its rows and is usually far fewer.
171    groups: BTreeMap<Value, BTreeMap<Value, u64>>,
172    /// `group_by`, for [`Agg::Sum`]: per group, its running total and how many rows carry it.
173    ///
174    /// Not [`Cell::groups`], and the difference is the operator's whole cost argument. A sum does
175    /// not care which distinct values its group holds, so keeping them would be memory and a tree
176    /// walk spent on a question nobody asked; a total moves by `+n` and `-n` and is read in `O(1)`.
177    /// The row count rides along because a total of `0` and *no rows at all* are different answers
178    /// — the first is a group, the second is a key the arrangement must not hold.
179    ///
180    /// Wider than the answer, for `list_sum`'s reason: the sum is exact and the failure is a
181    /// property of the total rather than of the order it was reached in, so the accumulator must be
182    /// able to hold what the answer cannot.
183    totals: BTreeMap<Value, (i128, u64)>,
184}
185
186/// What one [`Engine::render`] cost, in units that do not depend on the machine.
187///
188/// Wall-clock is measured in the harness; this is what a test asserts on, because "the count did
189/// not visit every row" is the claim, and a timing assertion in CI is a flake.
190#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
191pub struct Work {
192    /// Per-element functions applied — the `f` of a `map_list`, the predicate of a `filter_list`.
193    pub applications: u64,
194    /// Entries a delta operator inserted, updated or removed.
195    pub touched: u64,
196    /// Entries copied to hand a pointwise consumer a `Value::List`.
197    pub materialised: u64,
198    /// Pointwise operators re-evaluated.
199    pub recomputed: u64,
200    /// What the **backend** executed inside all of those, if it counts
201    /// ([`crate::backend::Steps`]); `0` when it does not, which is not the same as no work.
202    ///
203    /// The other four are what the engine did *to* its arrangements, and they stop at the boundary
204    /// of a call: one application is one application whether the function it ran read a field or
205    /// rebuilt a page. This is the inside of those calls, and it is what makes a plan that hides its
206    /// work in one opaque operator visible beside one that decomposed.
207    ///
208    /// **Not in [`Work::total`]**, deliberately. The other four are commensurable — entries and
209    /// applications, all `O(1)` each — and this is a different unit by two or three orders of
210    /// magnitude, so adding it would drown every gate that reads the total. A gate that wants it
211    /// asks for it by name.
212    pub steps: u64,
213}
214
215impl Work {
216    /// Everything that scales with the collection rather than with the change.
217    pub fn total(&self) -> u64 {
218        self.applications + self.touched + self.materialised + self.recomputed
219    }
220}
221
222/// A plan with every operator's code prepared: one per *program*, shared by every subscription.
223///
224/// The split between this and [`Engine`] is the difference between a plan and an arrangement, and
225/// it is load-bearing for §5.3's fanout. Preparing an operator means asking the backend to turn
226/// `Core` into something callable, which a compiling backend does expensively and even a
227/// tree-walker does by cloning the expression; doing it per subscriber cost about 90 KB of a
228/// subscription that then held 60 entries. A thousand subscribers share one of these.
229pub struct Prepared {
230    plan: Arc<Plan>,
231    /// The pointwise operators' bodies and the collection operators' per-element functions.
232    code: Vec<Option<Callable>>,
233    /// One per operator, in the order [`crate::plan::Op::funs`] gives them: a `map_list` has one,
234    /// a `group_by` has its key and its projection, everything else has none.
235    funs: Vec<Vec<Callable>>,
236    /// Constants, evaluated once here and never recomputed.
237    consts: Vec<Option<Value>>,
238    /// The backend's step counter, taken once at prepare time — see [`crate::backend::Steps`].
239    ///
240    /// Held here rather than passed to [`Engine::render`] because it is a property of the executor
241    /// the plan was prepared against, and an engine that was handed a different one would report
242    /// somebody else's arithmetic.
243    steps: Option<Arc<dyn crate::backend::Steps>>,
244}
245
246impl Prepared {
247    pub fn new(plan: Arc<Plan>, backend: &dyn Backend) -> Result<Prepared, ExecError> {
248        let n = plan.nodes.len();
249        let mut code: Vec<Option<Callable>> = Vec::with_capacity(n);
250        let mut funs: Vec<Vec<Callable>> = Vec::with_capacity(n);
251        for node in &plan.nodes {
252            code.push(match &node.op {
253                Op::Pointwise { code } => Some(backend.function(code)?),
254                _ => None,
255            });
256            let mut prepared = Vec::new();
257            for f in node.op.funs() {
258                prepared.push(backend.function(&f.code)?);
259            }
260            funs.push(prepared);
261        }
262        let mut consts: Vec<Option<Value>> = vec![None; n];
263        for (&id, expr) in &plan.constants {
264            consts[id] = Some(backend.constant(expr)?);
265        }
266        Ok(Prepared {
267            plan,
268            code,
269            funs,
270            consts,
271            steps: backend.steps(),
272        })
273    }
274
275    /// Compile and prepare a sliced program's view in one step.
276    pub fn compile(placed: &Placed, backend: &dyn Backend) -> Result<Prepared, ExecError> {
277        Prepared::new(Arc::new(Plan::compile(placed)), backend)
278    }
279
280    pub fn plan(&self) -> &Arc<Plan> {
281        &self.plan
282    }
283}
284
285/// One subscriber's arrangements over a [`Prepared`] plan.
286pub struct Engine {
287    prepared: Arc<Prepared>,
288    cells: Vec<Cell>,
289    /// Which of the plan's operators this engine computes and holds.
290    ///
291    /// All of them for a standalone engine. For a subscriber attached to a [`SharedDataflow`] it is
292    /// exactly the `per_session` nodes: the rest arrive from upstream, held once between every
293    /// subscriber, which is §5.3's sentence.
294    owns: Arc<[bool]>,
295    /// Whether any state at all has been established. Cleared by an error, so the next render
296    /// rebuilds rather than trusting a half-updated arrangement.
297    warm: bool,
298    /// The shared version this engine last rendered against, so the changes it has not yet seen can
299    /// be found. Meaningless for a standalone engine, which has no upstream to lag behind.
300    seen: u64,
301    /// This engine's place in a [`SharedDataflow`]'s reader set, for as long as it lives.
302    ///
303    /// `None` for a standalone engine, which owns every operator and has nobody to tell when it
304    /// goes away.
305    attached: Option<Attachment>,
306    work: Work,
307}
308
309/// A subscriber's membership of a shared dataflow's reader set.
310///
311/// Two facts the dataflow cannot learn any other way: that this reader exists — so the
312/// arrangements are not dropped underneath it — and how far behind it is, which is what bounds
313/// how much change history is worth keeping.
314///
315/// The frontier is an atomic rather than an entry in a map under the dataflow's lock, because it
316/// is written on **every** render and read only when the dataflow advances. A map would make the
317/// hot path take a write lock and serialise the concurrent renders §5.3 exists to allow.
318struct Attachment {
319    shared: Arc<SharedDataflow>,
320    id: ReaderId,
321    /// The version this reader has rendered up to, or [`UNRENDERED`].
322    frontier: Arc<AtomicU64>,
323}
324
325impl Drop for Engine {
326    fn drop(&mut self) {
327        if let Some(a) = &self.attached {
328            a.shared.detach(a.id);
329        }
330    }
331}
332
333impl Engine {
334    /// A fresh subscriber's view over a plan the program prepared once, computing every operator
335    /// itself.
336    pub fn new(prepared: Arc<Prepared>) -> Engine {
337        let owns: Arc<[bool]> = (0..prepared.plan.nodes.len()).map(|_| true).collect();
338        Engine::for_nodes(prepared, owns)
339    }
340
341    /// A subscriber's half of a plan whose shared prefix a [`SharedDataflow`] maintains.
342    ///
343    /// It owns the `per_session` operators and nothing else. Rendering it requires the shared side
344    /// — [`SharedDataflow::render`] — because the operators it does not own are where its inputs
345    /// come from.
346    pub fn subscriber(prepared: Arc<Prepared>) -> Engine {
347        let owns: Arc<[bool]> = prepared.plan.nodes.iter().map(|n| n.per_session).collect();
348        Engine::for_nodes(prepared, owns)
349    }
350
351    fn for_nodes(prepared: Arc<Prepared>, owns: Arc<[bool]>) -> Engine {
352        let mut cells: Vec<Cell> = (0..prepared.plan.nodes.len())
353            .map(|_| Cell::default())
354            .collect();
355        for (i, v) in prepared.consts.iter().enumerate() {
356            if let Some(v) = v {
357                if owns[i] {
358                    cells[i].out = Out::Val(v.clone());
359                }
360            }
361        }
362        Engine {
363            prepared,
364            cells,
365            owns,
366            warm: false,
367            seen: 0,
368            attached: None,
369            work: Work::default(),
370        }
371    }
372
373    /// Whether this engine computes an operator itself, rather than reading it from upstream.
374    fn owns(&self, id: OpId) -> bool {
375        self.owns[id]
376    }
377
378    pub fn plan(&self) -> &Arc<Plan> {
379        &self.prepared.plan
380    }
381
382    /// What the last [`Engine::render`] cost.
383    pub fn work(&self) -> Work {
384        self.work
385    }
386
387    /// How many entries every arrangement is holding — §5.3's per-session memory, in the unit that
388    /// scales.
389    pub fn arranged(&self) -> u64 {
390        self.arrangement_entries(|_| true)
391    }
392
393    /// The same count, restricted to arrangements that do **not** read the session.
394    ///
395    /// This is the part §5.3 says a thousand subscribers should hold *once* between them. A
396    /// subscriber attached to a [`SharedDataflow`] does not own those operators at all, so this is
397    /// zero for it and the entries are counted once, on [`SharedDataflow::arranged`].
398    pub fn arranged_shared(&self) -> u64 {
399        self.arrangement_entries(|per_session| !per_session)
400    }
401
402    fn arrangement_entries(&self, want: impl Fn(bool) -> bool) -> u64 {
403        self.cells
404            .iter()
405            .enumerate()
406            .filter(|(i, _)| self.owns[*i])
407            .map(|(i, c)| match &c.out {
408                Out::Arr(a) if want(self.prepared.plan.nodes[i].per_session) => {
409                    a.entries.len() as u64
410                }
411                _ => 0,
412            })
413            .sum()
414    }
415
416    /// Discard everything. The next render rebuilds from the state it is given.
417    pub fn reset(&mut self) {
418        for (i, cell) in self.cells.iter_mut().enumerate() {
419            *cell = Cell::default();
420            // A constant's value is still valid — only the arrangements are suspect.
421            if let Some(v) = &self.prepared.consts[i] {
422                if self.owns[i] {
423                    cell.out = Out::Val(v.clone());
424                }
425            }
426        }
427        self.warm = false;
428        self.seen = 0;
429    }
430
431    /// Render this subscriber's view of a state, maintaining whatever the plan can maintain.
432    ///
433    /// Correct for *any* state, not only the successor of the last one: an operator that cannot
434    /// derive a delta rebuilds. That matters because a reconnecting subscriber is rendered against
435    /// an older state (`beck-rt`'s resumption path), and an engine that assumed monotonic progress
436    /// would quietly serve it the wrong page.
437    pub fn render(
438        &mut self,
439        state: &Value,
440        session: &Value,
441        presence: &Value,
442    ) -> Result<Value, ExecError> {
443        self.render_from(None, state, session, presence, &crate::edge::no_awareness())
444    }
445
446    /// The same render, against both rosters the caller may be keeping.
447    ///
448    /// [`Engine::render`] passes an empty one, which is what a caller with no connection registry
449    /// holds; a program whose page reads `awareness` is rendered through here.
450    pub fn render_all(
451        &mut self,
452        state: &Value,
453        session: &Value,
454        presence: &Value,
455        aware: &Value,
456    ) -> Result<Value, ExecError> {
457        self.render_from(None, state, session, presence, aware)
458    }
459
460    /// The same render, with the operators this engine does not own arriving from upstream.
461    fn render_from(
462        &mut self,
463        up: Option<Upstream<'_>>,
464        state: &Value,
465        session: &Value,
466        presence: &Value,
467        aware: &Value,
468    ) -> Result<Value, ExecError> {
469        self.work = Work::default();
470        let before = self.steps_now();
471        let out = self
472            .tick(up, state, session, presence, aware)
473            .and_then(|()| self.materialise(up, self.prepared.plan.root));
474        // Charged even when the render failed, and before `reset` throws the arrangements away: a
475        // render that ran out of fuel is the one whose cost a reader most wants to see.
476        self.work.steps = self.steps_now().saturating_sub(before);
477        match out {
478            Ok(v) => {
479                self.warm = true;
480                Ok(v)
481            }
482            Err(e) => {
483                // A failed per-element function leaves an arrangement holding entries from two
484                // different states. Nothing downstream could detect that, so it is thrown away.
485                self.reset();
486                Err(e)
487            }
488        }
489    }
490
491    /// What the backend has executed so far, or zero when it does not count.
492    fn steps_now(&self) -> u64 {
493        self.prepared.steps.as_ref().map_or(0, |s| s.taken())
494    }
495
496    /// Advance the operators this engine owns, without assembling a page from them.
497    ///
498    /// This is the shared half of [`SharedDataflow`]: the root of the plan is per-session and this
499    /// engine does not own it, so there is nothing at the top to materialise.
500    fn advance(&mut self, state: &Value) -> Result<(), ExecError> {
501        self.work = Work::default();
502        let before = self.steps_now();
503        // The shared half owns no `Op::Presence` and no `Op::Awareness` — everything downstream of
504        // one is per-subscriber — so the values it would be given are never read.
505        let out = self.tick(None, state, &Value::Unit, &Value::Unit, &Value::Unit);
506        self.work.steps = self.steps_now().saturating_sub(before);
507        match out {
508            Ok(()) => {
509                self.warm = true;
510                Ok(())
511            }
512            Err(e) => {
513                self.reset();
514                Err(e)
515            }
516        }
517    }
518
519    fn tick(
520        &mut self,
521        up: Option<Upstream<'_>>,
522        state: &Value,
523        session: &Value,
524        presence: &Value,
525        aware: &Value,
526    ) -> Result<(), ExecError> {
527        let cold = !self.warm;
528        // The plan is behind an `Arc`, so this is one refcount rather than a clone of every
529        // operator's `Core` — which is what matching on `self.plan` directly would have cost, once
530        // per node per event.
531        let plan = self.prepared.plan.clone();
532        for id in 0..plan.nodes.len() {
533            // Not ours: it belongs to the shared dataflow, and reading it goes through `up`.
534            if !self.owns(id) {
535                continue;
536            }
537            match &plan.nodes[id].op {
538                Op::State => {
539                    self.cells[id].rebuilt = false;
540                    // Always "changed": the caller renders because the fold moved, and proving it
541                    // did not would cost a structural comparison of the whole accumulator — the
542                    // recount this engine exists to remove. Every consumer below is either a field
543                    // read (`O(1)`) or a `map_values` (`O(δ log n)`).
544                    self.cells[id].out = Out::Val(state.clone());
545                    self.cells[id].changed = true;
546                }
547                Op::Session => {
548                    self.cells[id].rebuilt = false;
549                    let changed =
550                        cold || !matches!(&self.cells[id].out, Out::Val(v) if same(v, session));
551                    self.cells[id].out = Out::Val(session.clone());
552                    self.cells[id].changed = changed;
553                }
554                // Compared rather than assumed changed, like the session and unlike the
555                // accumulator: most renders are provoked by an event rather than by a connection,
556                // so the common case is one comparison of two identical rosters and nothing below
557                // this operator re-runs.
558                Op::Presence => {
559                    self.cells[id].rebuilt = false;
560                    let changed =
561                        cold || !matches!(&self.cells[id].out, Out::Val(v) if same(v, presence));
562                    self.cells[id].out = Out::Val(presence.clone());
563                    self.cells[id].changed = changed;
564                }
565                // Compared for the same reason as the roster, and with more at stake: a cursor
566                // moves far more often than a connection does, so most events arrive with the
567                // awareness map unchanged and nothing below this re-runs.
568                Op::Awareness => {
569                    self.cells[id].rebuilt = false;
570                    let changed =
571                        cold || !matches!(&self.cells[id].out, Out::Val(v) if same(v, aware));
572                    self.cells[id].out = Out::Val(aware.clone());
573                    self.cells[id].changed = changed;
574                }
575                Op::Const => {
576                    self.cells[id].rebuilt = false;
577                    self.cells[id].changed = cold;
578                }
579                Op::Pointwise { .. } => self.pointwise(up, id, cold)?,
580                Op::MapValues => self.map_values(up, id, cold)?,
581                Op::MapList { f } => self.map_list(up, id, f, cold)?,
582                Op::FilterList { f } => self.filter_list(up, id, f, cold)?,
583                // One function for the two, because the arrangement is the same one: `f(x)`
584                // followed by the input's key. What differs is who reads it — `sort_by`'s consumer
585                // iterates it, `arrange_by`'s probes it — and `Op::ArrangeBy`'s own documentation
586                // says why that is still two operators.
587                Op::SortBy { f } | Op::ArrangeBy { key: f } => self.sort_by(up, id, f, cold)?,
588                Op::Concat => self.concat(up, id, cold)?,
589                Op::Flatten => self.flatten(up, id, None, cold)?,
590                Op::FlatMap { f } => self.flatten(up, id, Some(f), cold)?,
591                Op::Count => self.aggregate(up, id, cold, false)?,
592                Op::IsEmpty => self.aggregate(up, id, cold, true)?,
593                Op::Join { key, matched } => self.join(up, id, key, *matched, cold)?,
594                Op::GroupBy { key, of, agg } => self.group_by(up, id, key, of, *agg, cold)?,
595                Op::Restrict { key, keep } => self.restrict(up, id, key, *keep, cold)?,
596                Op::Distinct => self.distinct(up, id, cold)?,
597            }
598        }
599        Ok(())
600    }
601
602    // ---------------------------------------------------------------------------------------
603    // Operators
604    // ---------------------------------------------------------------------------------------
605
606    fn pointwise(
607        &mut self,
608        up: Option<Upstream<'_>>,
609        id: OpId,
610        cold: bool,
611    ) -> Result<(), ExecError> {
612        self.cells[id].rebuilt = false;
613        // An `Arc` bump, not a copy: this runs for every pointwise operator on every tick.
614        let plan = self.prepared.plan.clone();
615        let inputs = &plan.nodes[id].inputs;
616        if !cold && !inputs.iter().any(|&i| self.changed_of(up, i)) {
617            self.cells[id].changed = false;
618            return Ok(());
619        }
620        let mut args = Vec::with_capacity(inputs.len());
621        for &i in inputs {
622            args.push(self.materialise(up, i)?);
623        }
624        let f = self.prepared.code[id]
625            .as_ref()
626            .ok_or_else(|| ExecError::new("a pointwise operator has no prepared body", Span::NONE))?
627            .clone();
628        let next = f(args)?;
629        self.work.recomputed += 1;
630        let changed = match &self.cells[id].out {
631            Out::Val(prev) => !same(prev, &next),
632            Out::Arr(_) => true,
633        };
634        self.cells[id].out = Out::Val(next);
635        self.cells[id].changed = changed || cold;
636        Ok(())
637    }
638
639    /// `map_values(m)` — the source. Every other operator's deltas descend from this one.
640    fn map_values(
641        &mut self,
642        up: Option<Upstream<'_>>,
643        id: OpId,
644        cold: bool,
645    ) -> Result<(), ExecError> {
646        let input = self.prepared.plan.nodes[id].inputs[0];
647        if !cold && !self.changed_of(up, input) {
648            self.cells[id].changed = false;
649            self.cells[id].changes.clear();
650            self.cells[id].rebuilt = false;
651            return Ok(());
652        }
653        let source = match self.out_of(up, input)? {
654            Out::Val(Value::Map(m)) => Some(m.clone()),
655            // Not a map. The plan said this was `map_values`, so the only way here is a program the
656            // checker would have refused; rebuild wholesale rather than guess.
657            _ => None,
658        };
659        let Some(next) = source else {
660            let whole = self.materialise(up, input)?;
661            let entries = list_entries(&whole);
662            return self.replace(id, entries);
663        };
664        let seen = if cold {
665            PMap::new()
666        } else {
667            self.cells[id].seen_map.clone()
668        };
669        let mut arr = if cold {
670            Arrangement::default()
671        } else {
672            match std::mem::take(&mut self.cells[id].out) {
673                Out::Arr(a) => a,
674                Out::Val(_) => Arrangement::default(),
675            }
676        };
677        let mut changes = Vec::new();
678        for c in seen.diff(&next) {
679            let key: Key = Arc::from(vec![c.key]);
680            match &c.new {
681                Some(v) => {
682                    arr.entries.insert(key.clone(), v.clone());
683                }
684                None => {
685                    arr.entries.remove(&key);
686                }
687            }
688            changes.push(Change {
689                key,
690                old: c.old,
691                new: c.new,
692            });
693        }
694        self.cells[id].seen_map = next;
695        self.publish(id, arr, changes, cold);
696        Ok(())
697    }
698
699    fn map_list(
700        &mut self,
701        up: Option<Upstream<'_>>,
702        id: OpId,
703        f: &Fun,
704        cold: bool,
705    ) -> Result<(), ExecError> {
706        let (incoming, rebuild) = self.incoming(up, id, 0, f, cold)?;
707        if incoming.is_empty() && !rebuild {
708            self.cells[id].changed = false;
709            self.cells[id].changes.clear();
710            // Cleared, and this is not housekeeping. `rebuilt` means "threw its arrangement away
711            // *this tick*"; leaving the cold start's `true` here made it mean "has ever rebuilt",
712            // and a rebuild is contagious downstream — so every operator below a collection that
713            // had stopped changing rebuilt on every event, for the life of the subscription.
714            // `concat` and `flatten` always cleared it; these three never did.
715            self.cells[id].rebuilt = false;
716            return Ok(());
717        }
718        let call = self.fun_of(id)?;
719        let captured = self.captures(up, f)?;
720        let mut arr = self.take_arrangement(id, rebuild);
721        let mut changes = Vec::new();
722        for c in incoming {
723            match c.new {
724                Some(v) => {
725                    let mut args = captured.clone();
726                    args.push(v);
727                    let mapped = call(args)?;
728                    self.work.applications += 1;
729                    let old = arr.entries.insert(c.key.clone(), mapped.clone());
730                    changes.push(Change {
731                        key: c.key,
732                        old,
733                        new: Some(mapped),
734                    });
735                }
736                None => {
737                    let old = arr.entries.remove(&c.key);
738                    if old.is_some() {
739                        changes.push(Change {
740                            key: c.key,
741                            old,
742                            new: None,
743                        });
744                    }
745                }
746            }
747        }
748        self.publish(id, arr, changes, rebuild);
749        Ok(())
750    }
751
752    fn filter_list(
753        &mut self,
754        up: Option<Upstream<'_>>,
755        id: OpId,
756        f: &Fun,
757        cold: bool,
758    ) -> Result<(), ExecError> {
759        let (incoming, rebuild) = self.incoming(up, id, 0, f, cold)?;
760        if incoming.is_empty() && !rebuild {
761            self.cells[id].changed = false;
762            self.cells[id].changes.clear();
763            // Cleared, and this is not housekeeping. `rebuilt` means "threw its arrangement away
764            // *this tick*"; leaving the cold start's `true` here made it mean "has ever rebuilt",
765            // and a rebuild is contagious downstream — so every operator below a collection that
766            // had stopped changing rebuilt on every event, for the life of the subscription.
767            // `concat` and `flatten` always cleared it; these three never did.
768            self.cells[id].rebuilt = false;
769            return Ok(());
770        }
771        let call = self.fun_of(id)?;
772        let captured = self.captures(up, f)?;
773        let mut arr = self.take_arrangement(id, rebuild);
774        let mut changes = Vec::new();
775        for c in incoming {
776            let keep = match &c.new {
777                Some(v) => {
778                    let mut args = captured.clone();
779                    args.push(v.clone());
780                    let verdict = call(args)?;
781                    self.work.applications += 1;
782                    verdict.as_bool().unwrap_or(false)
783                }
784                None => false,
785            };
786            if keep {
787                let v = c.new.expect("kept means present");
788                let old = arr.entries.insert(c.key.clone(), v.clone());
789                changes.push(Change {
790                    key: c.key,
791                    old,
792                    new: Some(v),
793                });
794            } else if let Some(old) = arr.entries.remove(&c.key) {
795                changes.push(Change {
796                    key: c.key,
797                    old: Some(old),
798                    new: None,
799                });
800            }
801        }
802        self.publish(id, arr, changes, rebuild);
803        Ok(())
804    }
805
806    /// `sort_by(xs, k)` — an ordered arrangement, maintained by insertion.
807    ///
808    /// The output key is `k(x)` followed by the input's key. That second component is what makes
809    /// the sort *stable* in the same way the recompute's is: two elements with equal keys keep the
810    /// order they had at the input, and "the order they had" is exactly the input's key.
811    fn sort_by(
812        &mut self,
813        up: Option<Upstream<'_>>,
814        id: OpId,
815        f: &Fun,
816        cold: bool,
817    ) -> Result<(), ExecError> {
818        let (incoming, rebuild) = self.incoming(up, id, 0, f, cold)?;
819        if incoming.is_empty() && !rebuild {
820            self.cells[id].changed = false;
821            self.cells[id].changes.clear();
822            // Cleared, and this is not housekeeping. `rebuilt` means "threw its arrangement away
823            // *this tick*"; leaving the cold start's `true` here made it mean "has ever rebuilt",
824            // and a rebuild is contagious downstream — so every operator below a collection that
825            // had stopped changing rebuilt on every event, for the life of the subscription.
826            // `concat` and `flatten` always cleared it; these three never did.
827            self.cells[id].rebuilt = false;
828            return Ok(());
829        }
830        let call = self.fun_of(id)?;
831        let captured = self.captures(up, f)?;
832        let mut arr = self.take_arrangement(id, rebuild);
833        if rebuild {
834            self.cells[id].positions.clear();
835        }
836        let mut positions = std::mem::take(&mut self.cells[id].positions);
837        let mut changes = Vec::new();
838        for c in incoming {
839            if let Some(was) = positions.remove(&c.key) {
840                if let Some(old) = arr.entries.remove(&was) {
841                    changes.push(Change {
842                        key: was,
843                        old: Some(old),
844                        new: None,
845                    });
846                }
847            }
848            let Some(v) = c.new else { continue };
849            let mut args = captured.clone();
850            args.push(v.clone());
851            let sort_key = call(args)?;
852            self.work.applications += 1;
853            let mut out_key: Vec<Value> = vec![sort_key];
854            out_key.extend(c.key.iter().cloned());
855            let out_key: Key = Arc::from(out_key);
856            arr.entries.insert(out_key.clone(), v.clone());
857            positions.insert(c.key, out_key.clone());
858            changes.push(Change {
859                key: out_key,
860                old: None,
861                new: Some(v),
862            });
863        }
864        self.cells[id].positions = positions;
865        self.publish(id, arr, changes, rebuild);
866        Ok(())
867    }
868
869    /// `concat_lists([a, b, …])` — a union of delta streams, keyed by which stream.
870    fn concat(&mut self, up: Option<Upstream<'_>>, id: OpId, cold: bool) -> Result<(), ExecError> {
871        let plan = self.prepared.plan.clone();
872        let inputs = &plan.nodes[id].inputs;
873        let rebuild = cold || inputs.iter().any(|&i| self.rebuilt_of(up, i));
874        let mut arr = self.take_arrangement(id, rebuild);
875        let mut changes = Vec::new();
876        for (slot, input) in inputs.iter().copied().enumerate() {
877            let incoming = self.feed(up, id, slot, input, rebuild)?;
878            for c in incoming {
879                let mut key: Vec<Value> = vec![Value::Int(slot as i64)];
880                key.extend(c.key.iter().cloned());
881                let key: Key = Arc::from(key);
882                match c.new {
883                    Some(v) => {
884                        let old = arr.entries.insert(key.clone(), v.clone());
885                        changes.push(Change {
886                            key,
887                            old,
888                            new: Some(v),
889                        });
890                    }
891                    None => {
892                        if let Some(old) = arr.entries.remove(&key) {
893                            changes.push(Change {
894                                key,
895                                old: Some(old),
896                                new: None,
897                            });
898                        }
899                    }
900                }
901            }
902        }
903        if changes.is_empty() && !rebuild {
904            self.cells[id].out = Out::Arr(arr);
905            self.cells[id].changed = false;
906            self.cells[id].changes.clear();
907            self.cells[id].rebuilt = false;
908            return Ok(());
909        }
910        self.publish(id, arr, changes, rebuild);
911        Ok(())
912    }
913
914    /// `concat_lists(xs)` where `xs` is a collection of lists — a flatten, and what every `for`
915    /// loop in a `ui:` block compiles to.
916    ///
917    /// The output key is the input's key followed by the position inside that element's list, so
918    /// one row's children move without disturbing anybody else's, and the order is the order the
919    /// recompute would have produced.
920    fn flatten(
921        &mut self,
922        up: Option<Upstream<'_>>,
923        id: OpId,
924        f: Option<&Fun>,
925        cold: bool,
926    ) -> Result<(), ExecError> {
927        let input = self.prepared.plan.nodes[id].inputs[0];
928        // With a function, the rebuild rule is `map_list`'s rather than `flatten`'s: a captured
929        // node that moved makes `f` a different function, so every element has to be reapplied.
930        let (incoming, rebuild) = match f {
931            Some(f) => self.incoming(up, id, 0, f, cold)?,
932            None => {
933                let rebuild = cold || self.rebuilt_of(up, input);
934                (self.feed(up, id, 0, input, rebuild)?, rebuild)
935            }
936        };
937        if incoming.is_empty() && !rebuild {
938            self.cells[id].changed = false;
939            self.cells[id].changes.clear();
940            self.cells[id].rebuilt = false;
941            return Ok(());
942        }
943        let call = match f {
944            Some(_) => Some(self.fun_of(id)?),
945            None => None,
946        };
947        let captured = match f {
948            Some(f) => self.captures(up, f)?,
949            None => Vec::new(),
950        };
951        let mut arr = self.take_arrangement(id, rebuild);
952        if rebuild {
953            self.cells[id].counts.clear();
954        }
955        let mut counts = std::mem::take(&mut self.cells[id].counts);
956        let mut changes = Vec::new();
957        for c in incoming {
958            if let Some(n) = counts.remove(&c.key) {
959                for i in 0..n {
960                    let key = inner_key(&c.key, i);
961                    if let Some(old) = arr.entries.remove(&key) {
962                        changes.push(Change {
963                            key,
964                            old: Some(old),
965                            new: None,
966                        });
967                    }
968                }
969            }
970            let Some(v) = c.new else { continue };
971            let v = match &call {
972                Some(call) => {
973                    let mut args = captured.clone();
974                    args.push(v);
975                    let out = call(args)?;
976                    self.work.applications += 1;
977                    out
978                }
979                None => v,
980            };
981            let items = v.as_list().cloned().unwrap_or_default();
982            for (i, item) in items.iter().enumerate() {
983                let key = inner_key(&c.key, i);
984                arr.entries.insert(key.clone(), item.clone());
985                changes.push(Change {
986                    key,
987                    old: None,
988                    new: Some(item.clone()),
989                });
990            }
991            counts.insert(c.key, items.len());
992        }
993        self.cells[id].counts = counts;
994        self.publish(id, arr, changes, rebuild);
995        Ok(())
996    }
997
998    /// The join a loop already contained — [`Op::Join`], and
999    /// [`docs/99`](../../../../../docs/99-the-data-tier-means-of-combination.md) §99.5's bilinear
1000    /// delta rule.
1001    ///
1002    /// Two streams arrive and both are `O(δ)`:
1003    ///
1004    /// * a **left** row that moved is looked up in the index once, which is one application of the
1005    ///   key function and one `BTreeMap` probe;
1006    /// * a **right** row that moved reaches exactly the left rows whose key it answers, through the
1007    ///   reverse index this operator keeps ([`Cell::back`]). Nothing scans the left collection.
1008    ///
1009    /// Left changes are applied first, and the right pass skips what they already touched: the
1010    /// index has advanced before this operator runs — the plan's nodes are in dependency order — so
1011    /// a left row re-looked-up in the first pass already has the answer the second would give it.
1012    ///
1013    /// The output holds one row per left row, matched or not, because that is what the expression
1014    /// this replaced returned: `map_get`'s `Option`, or `filter_list`'s list — never an absence.
1015    ///
1016    /// [`Matching::Group`] differs in one place and it is the right-hand pass: a group is answered
1017    /// by the *range* under its key, so a key that moved rebuilds the whole group rather than
1018    /// substituting the row that moved. That is the honest half of §99.9 item 3 — the scan over the
1019    /// collection is gone, the group's own size is not, and `group by` is what takes it.
1020    fn join(
1021        &mut self,
1022        up: Option<Upstream<'_>>,
1023        id: OpId,
1024        key: &Fun,
1025        matching: Matching,
1026        cold: bool,
1027    ) -> Result<(), ExecError> {
1028        let plan = self.prepared.plan.clone();
1029        let left = plan.nodes[id].inputs[0];
1030        let index = plan.nodes[id].inputs[1];
1031        // An index that is not an arrangement has no deltas to react to, so every tick it moves is
1032        // a rebuild. The decomposition only ever builds a `map_values` here, so this is the
1033        // correct-for-a-plan-nobody-writes path rather than one the corpus takes.
1034        let indexed = matches!(self.out_of(up, index)?, Out::Arr(_));
1035        let rebuild = cold
1036            || self.rebuilt_of(up, left)
1037            || self.rebuilt_of(up, index)
1038            || (!indexed && self.changed_of(up, index))
1039            || key.captures.iter().any(|&c| self.changed_of(up, c));
1040        let left_changes = self.feed(up, id, 0, left, rebuild)?;
1041        let right_changes = if rebuild || !indexed || !self.changed_of(up, index) {
1042            Vec::new()
1043        } else {
1044            self.changes_of(up, index)
1045        };
1046        if left_changes.is_empty() && right_changes.is_empty() && !rebuild {
1047            self.cells[id].changed = false;
1048            self.cells[id].changes.clear();
1049            self.cells[id].rebuilt = false;
1050            return Ok(());
1051        }
1052
1053        let call = self.fun_of(id)?;
1054        let captured = self.captures(up, key)?;
1055        let mut arr = self.take_arrangement(id, rebuild);
1056        if rebuild {
1057            self.cells[id].positions.clear();
1058            self.cells[id].back.clear();
1059        }
1060        // The count per key, brought up to date **before** either pass, because the index has
1061        // already advanced and both passes are about to ask it questions. A rebuild counts the
1062        // index once, which is what a rebuild is; otherwise it is ±1 per entry that moved.
1063        let mut tally = std::mem::take(&mut self.cells[id].tally);
1064        if matching == Matching::Count {
1065            if rebuild {
1066                tally.clear();
1067                if let Out::Arr(a) = self.out_of(up, index)? {
1068                    for k in a.entries.keys() {
1069                        if let Some(jk) = k.first() {
1070                            *tally.entry(jk.clone()).or_default() += 1;
1071                        }
1072                    }
1073                }
1074            } else {
1075                for c in &right_changes {
1076                    let Some(jk) = c.key.first() else { continue };
1077                    match (c.old.is_some(), c.new.is_some()) {
1078                        (false, true) => *tally.entry(jk.clone()).or_default() += 1,
1079                        // Saturating, and the saturation is unreachable rather than defensive: an
1080                        // entry cannot leave an index it never entered. It is written this way
1081                        // because the alternative is a panic in a render.
1082                        (true, false) => {
1083                            let left = tally.entry(jk.clone()).or_default();
1084                            *left = left.saturating_sub(1);
1085                            if *left == 0 {
1086                                tally.remove(jk);
1087                            }
1088                        }
1089                        _ => {}
1090                    }
1091                }
1092            }
1093        }
1094        // `positions` holds the join key each left row currently waits on — the same role it plays
1095        // for `sort_by`, which is where a row currently sits.
1096        let mut positions = std::mem::take(&mut self.cells[id].positions);
1097        let mut back = std::mem::take(&mut self.cells[id].back);
1098        let mut changes = Vec::new();
1099        let mut touched: BTreeSet<Key> = BTreeSet::new();
1100
1101        for c in left_changes {
1102            if let Some(was) = positions.remove(&c.key) {
1103                withdraw(&mut back, &was[0], &c.key);
1104                if let Some(old) = arr.entries.remove(&c.key) {
1105                    changes.push(Change {
1106                        key: c.key.clone(),
1107                        old: Some(old),
1108                        new: None,
1109                    });
1110                }
1111            }
1112            let Some(lv) = c.new else { continue };
1113            let mut args = captured.clone();
1114            args.push(lv.clone());
1115            let jk = call(args)?;
1116            self.work.applications += 1;
1117            let answer = self.answer(up, index, &jk, matching, &tally)?;
1118            let row = joined(lv, answer);
1119            arr.entries.insert(c.key.clone(), row.clone());
1120            positions.insert(c.key.clone(), Arc::from(vec![jk.clone()]));
1121            back.entry(jk).or_default().insert(c.key.clone());
1122            touched.insert(c.key.clone());
1123            changes.push(Change {
1124                key: c.key,
1125                old: None,
1126                new: Some(row),
1127            });
1128        }
1129
1130        // The index's key is the join key: that is what makes it an index rather than an
1131        // arrangement that happens to be beside this operator. Several changes may share one — a
1132        // group's are all under it — so the keys that moved are collected before anything is
1133        // answered, and each is answered once however many of its rows moved.
1134        let moved: BTreeSet<Value> = right_changes
1135            .iter()
1136            .filter_map(|c| c.key.first().cloned())
1137            .collect();
1138        for jk in moved {
1139            if !back.contains_key(&jk) {
1140                continue;
1141            }
1142            let answer = self.answer(up, index, &jk, matching, &tally)?;
1143            let waiting = back.get(&jk).expect("checked just above");
1144            for lk in waiting.iter() {
1145                if touched.contains(lk) {
1146                    continue;
1147                }
1148                let Some(old) = arr.entries.get(lk) else {
1149                    continue;
1150                };
1151                let Some(lv) = old.field(crate::relate::LEFT).cloned() else {
1152                    continue;
1153                };
1154                let row = joined(lv, answer.clone());
1155                let old = arr.entries.insert(lk.clone(), row.clone());
1156                changes.push(Change {
1157                    key: lk.clone(),
1158                    old,
1159                    new: Some(row),
1160                });
1161            }
1162        }
1163
1164        self.cells[id].positions = positions;
1165        self.cells[id].back = back;
1166        self.cells[id].tally = tally;
1167        self.publish(id, arr, changes, rebuild);
1168        Ok(())
1169    }
1170
1171    /// What one probe of the index returns, as the value the joined row's right half holds.
1172    ///
1173    /// The two [`Matching`]s read the same arrangement differently and that is the whole
1174    /// difference between them: a unique index is a point lookup and a group index is the range
1175    /// under one key. A range works because the `arrange_by` key's first component *is* the join
1176    /// key and a `BTreeMap`'s order is `Value`'s — which is also what `==` compares, so the range
1177    /// holds exactly the rows the predicate would have kept, in the order the collection held them.
1178    fn answer(
1179        &mut self,
1180        up: Option<Upstream<'_>>,
1181        index: OpId,
1182        jk: &Value,
1183        matching: Matching,
1184        tally: &BTreeMap<Value, u64>,
1185    ) -> Result<Value, ExecError> {
1186        // The whole of §99.9 item 6's first aggregate: a question about a group that the group does
1187        // not have to exist to answer.
1188        if matching == Matching::Count {
1189            return Ok(Value::Int(tally.get(jk).copied().unwrap_or(0) as i64));
1190        }
1191        if matching == Matching::Unique || matching == Matching::Total {
1192            let found = match self.out_of(up, index)? {
1193                Out::Arr(a) => a.entries.get(&key_of(jk)).cloned(),
1194                Out::Val(Value::Map(m)) => m.get(jk).cloned(),
1195                Out::Val(_) => None,
1196            };
1197            if matching == Matching::Unique {
1198                return Ok(found.map(Value::some).unwrap_or_else(Value::none));
1199            }
1200            return match found {
1201                // The sum of no numbers, which is the one place this probe differs from the one
1202                // above it: `list_min` of an empty group is `None` and `list_sum` of it is `0`.
1203                None => Ok(Value::Int(0)),
1204                Some(v) if v.as_int().is_some() => Ok(v),
1205                // A group whose total no `Int` holds. [`Op::GroupBy`] published that instead of
1206                // raising it so that a group nobody asks about cannot fail a render, which leaves
1207                // the raise here — at the site that asked, where the recompute's own `list_sum`
1208                // raises it, with the same words.
1209                Some(_) => Err(sum_overflowed()),
1210            };
1211        }
1212        let mut rows = Vec::new();
1213        // Only an arrangement can be probed by a range. The decomposition builds an `arrange_by`
1214        // here and that is one, so this is the correct-for-a-plan-nobody-writes path rather than
1215        // one the corpus takes — the same case the `indexed` test above covers.
1216        if let Out::Arr(a) = self.out_of(up, index)? {
1217            for (k, v) in a.entries.range(key_of(jk)..) {
1218                if k.first() != Some(jk) {
1219                    break;
1220                }
1221                rows.push(v.clone());
1222            }
1223        }
1224        // A group is entries copied out of an arrangement to hand a consumer a `Value::List`,
1225        // which is what `Work::materialised` counts — so it is counted there, and the scaling
1226        // gates that exclude `materialised` keep excluding assembly rather than starting to
1227        // include it.
1228        self.work.materialised += rows.len() as u64;
1229        Ok(Value::list(rows))
1230    }
1231
1232    /// The rows one collection keeps because another one answers their key, or because it does
1233    /// not — [`Op::Restrict`], and
1234    /// [`docs/99`](../../../../../docs/99-the-data-tier-means-of-combination.md) §99.9 item 7's
1235    /// difference and its complement.
1236    ///
1237    /// [`Engine::join`]'s delta rule with one thing taken out and one thing put in. Taken out: the
1238    /// probe returns a *bool*, so nothing is copied out of the index and no row is built. Put in:
1239    /// a row this operator dropped is not in its arrangement, so when the index entry that dropped
1240    /// it leaves, the value has to come from somewhere — and that somewhere is the **left input**,
1241    /// which is holding it either as an arrangement or as the shadow [`Engine::feed`] keeps of a
1242    /// plain list. So this operator's own state is a join key per left row and the reverse index,
1243    /// and it never copies the collection it filters.
1244    ///
1245    /// Both passes are `O(δ log n)`. The right one is the half that cannot be got from a left-only
1246    /// rule and cannot be seen by any test over one collection: an entry arriving in the index
1247    /// takes exactly the rows waiting on its key out of a difference, and puts exactly those rows
1248    /// into an intersection.
1249    fn restrict(
1250        &mut self,
1251        up: Option<Upstream<'_>>,
1252        id: OpId,
1253        key: &Fun,
1254        keep: Presence,
1255        cold: bool,
1256    ) -> Result<(), ExecError> {
1257        let plan = self.prepared.plan.clone();
1258        let left = plan.nodes[id].inputs[0];
1259        let index = plan.nodes[id].inputs[1];
1260        // [`Engine::join`]'s conditions, for [`Engine::join`]'s reasons.
1261        let indexed = matches!(self.out_of(up, index)?, Out::Arr(_));
1262        let rebuild = cold
1263            || self.rebuilt_of(up, left)
1264            || self.rebuilt_of(up, index)
1265            || (!indexed && self.changed_of(up, index))
1266            || key.captures.iter().any(|&c| self.changed_of(up, c));
1267        let left_changes = self.feed(up, id, 0, left, rebuild)?;
1268        let right_changes = if rebuild || !indexed || !self.changed_of(up, index) {
1269            Vec::new()
1270        } else {
1271            self.changes_of(up, index)
1272        };
1273        if left_changes.is_empty() && right_changes.is_empty() && !rebuild {
1274            self.cells[id].changed = false;
1275            self.cells[id].changes.clear();
1276            self.cells[id].rebuilt = false;
1277            return Ok(());
1278        }
1279
1280        let call = self.fun_of(id)?;
1281        let captured = self.captures(up, key)?;
1282        let mut arr = self.take_arrangement(id, rebuild);
1283        if rebuild {
1284            self.cells[id].positions.clear();
1285            self.cells[id].back.clear();
1286        }
1287        let mut positions = std::mem::take(&mut self.cells[id].positions);
1288        let mut back = std::mem::take(&mut self.cells[id].back);
1289        let mut changes = Vec::new();
1290        let mut touched: BTreeSet<Key> = BTreeSet::new();
1291
1292        for c in left_changes {
1293            if let Some(was) = positions.remove(&c.key) {
1294                withdraw(&mut back, &was[0], &c.key);
1295            }
1296            let kept = match &c.new {
1297                Some(lv) => {
1298                    let mut args = captured.clone();
1299                    args.push(lv.clone());
1300                    let jk = call(args)?;
1301                    self.work.applications += 1;
1302                    positions.insert(c.key.clone(), key_of(&jk));
1303                    back.entry(jk.clone()).or_default().insert(c.key.clone());
1304                    touched.insert(c.key.clone());
1305                    keep.keeps(self.holds(up, index, &jk)?)
1306                }
1307                None => false,
1308            };
1309            if kept {
1310                let lv = c.new.expect("kept means present");
1311                let old = arr.entries.insert(c.key.clone(), lv.clone());
1312                changes.push(Change {
1313                    key: c.key,
1314                    old,
1315                    new: Some(lv),
1316                });
1317            } else if let Some(old) = arr.entries.remove(&c.key) {
1318                changes.push(Change {
1319                    key: c.key,
1320                    old: Some(old),
1321                    new: None,
1322                });
1323            }
1324        }
1325
1326        // The index's key is the probe key, so several entries never share one here — but the
1327        // change stream may still carry a key twice in a tick, and answering it once per key
1328        // rather than once per change is what keeps the two operators' right-hand passes the same
1329        // shape.
1330        let moved: BTreeSet<Value> = right_changes
1331            .iter()
1332            .filter_map(|c| c.key.first().cloned())
1333            .collect();
1334        for jk in moved {
1335            if !back.contains_key(&jk) {
1336                continue;
1337            }
1338            let kept = keep.keeps(self.holds(up, index, &jk)?);
1339            let waiting: Vec<Key> = back
1340                .get(&jk)
1341                .expect("checked just above")
1342                .iter()
1343                .cloned()
1344                .collect();
1345            for lk in waiting {
1346                // A row the left pass already handled was probed *after* the index advanced, so it
1347                // has the answer this pass would give it — [`Engine::join`]'s rule, unchanged.
1348                if touched.contains(&lk) || arr.entries.contains_key(&lk) == kept {
1349                    continue;
1350                }
1351                if kept {
1352                    // The value the filter dropped, read back from whoever is holding it.
1353                    let Some(lv) = self.left_value(up, id, left, &lk)? else {
1354                        continue;
1355                    };
1356                    arr.entries.insert(lk.clone(), lv.clone());
1357                    changes.push(Change {
1358                        key: lk,
1359                        old: None,
1360                        new: Some(lv),
1361                    });
1362                } else if let Some(old) = arr.entries.remove(&lk) {
1363                    changes.push(Change {
1364                        key: lk,
1365                        old: Some(old),
1366                        new: None,
1367                    });
1368                }
1369            }
1370        }
1371
1372        self.cells[id].positions = positions;
1373        self.cells[id].back = back;
1374        self.publish(id, arr, changes, rebuild);
1375        Ok(())
1376    }
1377
1378    /// The values in a collection, each once — [`Op::Distinct`], and
1379    /// [`docs/99`](../../../../../docs/99-the-data-tier-means-of-combination.md) §99.9 item 7's
1380    /// second half.
1381    ///
1382    /// The operator publishes one entry per distinct value, at the **smallest input key** holding
1383    /// it, so iterating the output gives the values in the order they were first seen — which is
1384    /// what `list_unique` returns of a list, and therefore what the recompute this is held to
1385    /// returns.
1386    ///
1387    /// Three passes, and the first is why there are three: settling a value means comparing where
1388    /// it *was* published with where it belongs now, and applying the changes first destroys the
1389    /// first half of that comparison. So the affected values' current positions are read before
1390    /// anything moves, the changes are applied, and then each affected value is settled once
1391    /// however many of its occurrences moved.
1392    ///
1393    /// Two maps, both of them fields this engine already had. [`Cell::positions`] holds what each
1394    /// input key currently contributes, which is [`Engine::group_by`]'s reason for having it: a
1395    /// value that has gone cannot be recomputed from the change. [`Cell::back`] holds the input
1396    /// keys under each value — [`Engine::join`]'s reverse index with the roles read the other way
1397    /// round — and the smallest of them is the answer, so a first occurrence leaving **promotes**
1398    /// the next rather than dropping the value.
1399    fn distinct(
1400        &mut self,
1401        up: Option<Upstream<'_>>,
1402        id: OpId,
1403        cold: bool,
1404    ) -> Result<(), ExecError> {
1405        let input = self.prepared.plan.nodes[id].inputs[0];
1406        let rebuild = cold || self.rebuilt_of(up, input);
1407        let incoming = self.feed(up, id, 0, input, rebuild)?;
1408        if incoming.is_empty() && !rebuild {
1409            self.cells[id].changed = false;
1410            self.cells[id].changes.clear();
1411            self.cells[id].rebuilt = false;
1412            return Ok(());
1413        }
1414
1415        let mut arr = self.take_arrangement(id, rebuild);
1416        if rebuild {
1417            self.cells[id].positions.clear();
1418            self.cells[id].back.clear();
1419        }
1420        let mut positions = std::mem::take(&mut self.cells[id].positions);
1421        let mut back = std::mem::take(&mut self.cells[id].back);
1422
1423        // Where each affected value sits *now*, before anything moves.
1424        let mut was: BTreeMap<Value, Option<Key>> = BTreeMap::new();
1425        let note = |was: &mut BTreeMap<Value, Option<Key>>, v: &Value| {
1426            if !was.contains_key(v) {
1427                let at = back.get(v).and_then(|keys| keys.iter().next().cloned());
1428                was.insert(v.clone(), at);
1429            }
1430        };
1431        for c in &incoming {
1432            if let Some(before) = positions.get(&c.key) {
1433                note(&mut was, &before[0].clone());
1434            }
1435            if let Some(v) = &c.new {
1436                note(&mut was, v);
1437            }
1438        }
1439
1440        for c in incoming {
1441            if let Some(before) = positions.remove(&c.key) {
1442                withdraw(&mut back, &before[0], &c.key);
1443            }
1444            let Some(v) = c.new else { continue };
1445            back.entry(v.clone()).or_default().insert(c.key.clone());
1446            positions.insert(c.key, key_of(&v));
1447        }
1448
1449        let settling: Vec<(Value, Option<Key>, Option<Key>)> = was
1450            .into_iter()
1451            .filter_map(|(v, before)| {
1452                let now = back.get(&v).and_then(|keys| keys.iter().next().cloned());
1453                match before == now {
1454                    true => None,
1455                    false => Some((v, before, now)),
1456                }
1457            })
1458            .collect();
1459
1460        // **Every departure before any arrival**, and this is the one ordering constraint the
1461        // operator has. The key a value is leaving can be the key another value is arriving at —
1462        // one input row changing what it contributes is exactly that — and settling the arriving
1463        // value first would have the departing one's removal take the new entry back out. The
1464        // corpus-wide differential is what found it.
1465        let mut changes = Vec::new();
1466        for (_, before, _) in &settling {
1467            let Some(k) = before else { continue };
1468            if let Some(old) = arr.entries.remove(k) {
1469                changes.push(Change {
1470                    key: k.clone(),
1471                    old: Some(old),
1472                    new: None,
1473                });
1474            }
1475        }
1476        for (v, _, now) in &settling {
1477            let Some(k) = now else { continue };
1478            arr.entries.insert(k.clone(), v.clone());
1479            changes.push(Change {
1480                key: k.clone(),
1481                old: None,
1482                new: Some(v.clone()),
1483            });
1484        }
1485
1486        self.cells[id].positions = positions;
1487        self.cells[id].back = back;
1488        self.publish(id, arr, changes, rebuild);
1489        Ok(())
1490    }
1491
1492    /// Whether an index holds a key at all — [`Engine::restrict`]'s probe.
1493    ///
1494    /// [`Engine::answer`]'s sibling, and what it does *not* do is the operator's cost argument:
1495    /// nothing is read out of the index and nothing is copied, so a difference over a collection
1496    /// of a million rows moves no value that is not already moving.
1497    fn holds(&self, up: Option<Upstream<'_>>, index: OpId, jk: &Value) -> Result<bool, ExecError> {
1498        Ok(match self.out_of(up, index)? {
1499            Out::Arr(a) => a.entries.contains_key(&key_of(jk)),
1500            // The decomposition only ever builds a `map_values` here, so this is the
1501            // correct-for-a-plan-nobody-writes path rather than one the corpus takes.
1502            Out::Val(Value::Map(m)) => m.get(jk).is_some(),
1503            Out::Val(_) => false,
1504        })
1505    }
1506
1507    /// One left row's current value, from whoever is already holding it.
1508    ///
1509    /// [`Engine::restrict`] keeps no copy of its input, so this is where a row it dropped comes
1510    /// back from. An arrangement holds it under the same key the change carried; a plain list has
1511    /// no arrangement, and the shadow [`Engine::feed`] keeps in order to make deltas out of it is
1512    /// keyed the same way.
1513    fn left_value(
1514        &self,
1515        up: Option<Upstream<'_>>,
1516        id: OpId,
1517        left: OpId,
1518        lk: &Key,
1519    ) -> Result<Option<Value>, ExecError> {
1520        if let Out::Arr(a) = self.out_of(up, left)? {
1521            return Ok(a.entries.get(lk).cloned());
1522        }
1523        Ok(self.cells[id]
1524            .shadow
1525            .first()
1526            .and_then(|seen| seen.get(lk))
1527            .cloned())
1528    }
1529
1530    /// One value per group, maintained — [`Op::GroupBy`], and
1531    /// [`docs/99`](../../../../../docs/99-the-data-tier-means-of-combination.md) §99.9 item 6's
1532    /// other two aggregates.
1533    ///
1534    /// Per group, whatever its aggregate needs and no more: a multiset of what its rows projected
1535    /// to for an extreme ([`Cell::groups`]), a running total for a sum ([`Cell::totals`]). A row
1536    /// that arrived is one insert and one row that left is one removal, so the operator costs `2δ`
1537    /// applications and `O(δ log n)` on the trees — the group is never assembled and the collection
1538    /// is never scanned.
1539    ///
1540    /// **What it publishes is the change in the answer, not the change in the group.** A row added
1541    /// behind the extreme moves the multiset and not the aggregate, so no change is emitted and
1542    /// nothing downstream of this operator runs at all. That is the difference between an aggregate
1543    /// and a `filter_list` a consumer measures: the second reports every event that touched the
1544    /// group, whether or not it changed the answer. [`Agg::Sum`] is the aggregate for which the
1545    /// two coincide — a row that joins a group moves its total — so it is the one that never takes
1546    /// that discount, and the operator's cost is the same either way.
1547    ///
1548    /// **`min` and `max` cost the same**, which §99.9 item 6 expected not to be true. The
1549    /// asymmetry it forecast is real of a *prefix range of somebody else's arrangement* — bounding
1550    /// `(g, y)` above needs a successor of an arbitrary [`Value`] and there is none — and it is not
1551    /// real here, because this tree is keyed by the projection alone and per group, so both of its
1552    /// ends are one `BTreeMap` call.
1553    fn group_by(
1554        &mut self,
1555        up: Option<Upstream<'_>>,
1556        id: OpId,
1557        key: &Fun,
1558        of: &Fun,
1559        agg: Agg,
1560        cold: bool,
1561    ) -> Result<(), ExecError> {
1562        let input = self.prepared.plan.nodes[id].inputs[0];
1563        // Both functions, because either one capturing something that moved makes every row's
1564        // contribution a different value — the rebuild rule `Engine::incoming` states for one.
1565        let rebuild = cold
1566            || self.rebuilt_of(up, input)
1567            || key
1568                .captures
1569                .iter()
1570                .chain(of.captures.iter())
1571                .any(|&c| self.changed_of(up, c));
1572        let incoming = self.feed(up, id, 0, input, rebuild)?;
1573        if incoming.is_empty() && !rebuild {
1574            self.cells[id].changed = false;
1575            self.cells[id].changes.clear();
1576            self.cells[id].rebuilt = false;
1577            return Ok(());
1578        }
1579
1580        let group_of = self.fun_of(id)?;
1581        let project = self.nth_fun_of(id, 1)?;
1582        let group_captures = self.captures(up, key)?;
1583        let project_captures = self.captures(up, of)?;
1584        let mut arr = self.take_arrangement(id, rebuild);
1585        if rebuild {
1586            self.cells[id].groups.clear();
1587            self.cells[id].totals.clear();
1588        }
1589        let mut groups = std::mem::take(&mut self.cells[id].groups);
1590        let mut totals = std::mem::take(&mut self.cells[id].totals);
1591        let mut positions = std::mem::take(&mut self.cells[id].positions);
1592        // The groups whose multiset moved, so each is answered once however many of its rows did.
1593        let mut moved: BTreeSet<Value> = BTreeSet::new();
1594
1595        for c in incoming {
1596            if let Some(was) = positions.remove(&c.key) {
1597                let (g, contributed) = (was[0].clone(), &was[1]);
1598                if agg == Agg::Sum {
1599                    if let Some((total, rows)) = totals.get_mut(&g) {
1600                        *total = total
1601                            .checked_sub(contribution(contributed)?)
1602                            .ok_or_else(sum_overflowed)?;
1603                        // Saturating for the reason the multiset's decrement is, one arm down.
1604                        *rows = rows.saturating_sub(1);
1605                        if *rows == 0 {
1606                            totals.remove(&g);
1607                        }
1608                    }
1609                } else if let Some(multiset) = groups.get_mut(&g) {
1610                    if let Some(n) = multiset.get_mut(contributed) {
1611                        // Saturating for `Engine::join`'s reason: a row cannot leave a group it
1612                        // never joined, so the saturation is unreachable rather than defensive, and
1613                        // the alternative to writing it this way is a panic in a render.
1614                        *n = n.saturating_sub(1);
1615                        if *n == 0 {
1616                            multiset.remove(contributed);
1617                        }
1618                    }
1619                    // A group with no rows left holds nothing, rather than an empty map keyed by a
1620                    // value the data no longer contains — which would be `back`'s leak
1621                    // (`Engine::join`) arrived at from the other side.
1622                    if multiset.is_empty() {
1623                        groups.remove(&g);
1624                    }
1625                }
1626                moved.insert(g);
1627            }
1628            let Some(v) = c.new else { continue };
1629            let mut args = group_captures.clone();
1630            args.push(v.clone());
1631            let g = group_of(args)?;
1632            let mut args = project_captures.clone();
1633            args.push(v);
1634            let contributed = project(args)?;
1635            self.work.applications += 2;
1636            if agg == Agg::Sum {
1637                let entry = totals.entry(g.clone()).or_insert((0, 0));
1638                entry.0 = entry
1639                    .0
1640                    .checked_add(contribution(&contributed)?)
1641                    .ok_or_else(sum_overflowed)?;
1642                entry.1 += 1;
1643            } else {
1644                *groups
1645                    .entry(g.clone())
1646                    .or_default()
1647                    .entry(contributed.clone())
1648                    .or_default() += 1;
1649            }
1650            positions.insert(c.key, Arc::from(vec![g.clone(), contributed]));
1651            moved.insert(g);
1652        }
1653
1654        let mut changes = Vec::new();
1655        for g in moved {
1656            let now = match agg {
1657                Agg::Min => groups.get(&g).and_then(|m| m.keys().next().cloned()),
1658                Agg::Max => groups.get(&g).and_then(|m| m.keys().next_back().cloned()),
1659                // A total no `Int` holds is **published rather than raised**: this operator
1660                // maintains every group and the recompute only ever sums the groups the loop
1661                // reaches, so failing here would fail renders that never asked. The entry is not
1662                // an `Int`, and [`Matching::Total`]'s probe is where that raises.
1663                Agg::Sum => totals.get(&g).map(|&(total, _)| {
1664                    i64::try_from(total)
1665                        .map(Value::Int)
1666                        .unwrap_or_else(|_| Value::none())
1667                }),
1668            };
1669            let out_key: Key = Arc::from(vec![g]);
1670            match now {
1671                Some(v) => {
1672                    let old = arr.entries.insert(out_key.clone(), v.clone());
1673                    // The whole point of the operator, in one condition: the answer moved or it did
1674                    // not, and a group that was touched without its extreme changing publishes
1675                    // nothing.
1676                    if old.as_ref() != Some(&v) {
1677                        changes.push(Change {
1678                            key: out_key,
1679                            old,
1680                            new: Some(v),
1681                        });
1682                    }
1683                }
1684                None => {
1685                    if let Some(old) = arr.entries.remove(&out_key) {
1686                        changes.push(Change {
1687                            key: out_key,
1688                            old: Some(old),
1689                            new: None,
1690                        });
1691                    }
1692                }
1693            }
1694        }
1695
1696        self.cells[id].groups = groups;
1697        self.cells[id].totals = totals;
1698        self.cells[id].positions = positions;
1699        if changes.is_empty() && !rebuild {
1700            self.cells[id].out = Out::Arr(arr);
1701            self.cells[id].changed = false;
1702            self.cells[id].changes.clear();
1703            self.cells[id].rebuilt = false;
1704            return Ok(());
1705        }
1706        self.publish(id, arr, changes, rebuild);
1707        Ok(())
1708    }
1709
1710    /// `list_len` and `list_is_empty`: read the arrangement's size.
1711    ///
1712    /// This is §3.8's sentence, mechanised. It reads `entries.len()` — `O(1)` — and, crucially,
1713    /// never calls [`Engine::materialise`], so a program that only asks how many there are never
1714    /// pays for a list of them.
1715    fn aggregate(
1716        &mut self,
1717        up: Option<Upstream<'_>>,
1718        id: OpId,
1719        cold: bool,
1720        emptiness: bool,
1721    ) -> Result<(), ExecError> {
1722        self.cells[id].rebuilt = false;
1723        let input = self.prepared.plan.nodes[id].inputs[0];
1724        if !cold && !self.changed_of(up, input) {
1725            self.cells[id].changed = false;
1726            return Ok(());
1727        }
1728        let n = match self.out_of(up, input)? {
1729            Out::Arr(a) => a.entries.len(),
1730            Out::Val(Value::List(xs)) => xs.len(),
1731            Out::Val(Value::Map(m)) => m.len(),
1732            Out::Val(_) => {
1733                let whole = self.materialise(up, input)?;
1734                whole.as_list().map(|l| l.len()).unwrap_or(0)
1735            }
1736        };
1737        let next = if emptiness {
1738            Value::Bool(n == 0)
1739        } else {
1740            Value::Int(n as i64)
1741        };
1742        let changed = match &self.cells[id].out {
1743            Out::Val(prev) => !same(prev, &next),
1744            Out::Arr(_) => true,
1745        };
1746        self.cells[id].out = Out::Val(next);
1747        self.cells[id].changed = changed || cold;
1748        Ok(())
1749    }
1750
1751    // ---------------------------------------------------------------------------------------
1752    // Plumbing
1753    // ---------------------------------------------------------------------------------------
1754
1755    /// The changes arriving at a collection operator, and whether it has to rebuild.
1756    ///
1757    /// Three things force a rebuild, and only the first is interesting:
1758    ///
1759    /// * the operator's per-element function *captured* something that moved —
1760    ///   `lambda t: t.owner == session.actor` is a different predicate for a different session, so
1761    ///   every element has to be reconsidered. This is the one case where the answer genuinely does
1762    ///   depend on the whole collection;
1763    /// * an input rebuilt, because a rebuild's changes are inserts with no matching removals;
1764    /// * the engine is cold.
1765    fn incoming(
1766        &mut self,
1767        up: Option<Upstream<'_>>,
1768        id: OpId,
1769        slot: usize,
1770        f: &Fun,
1771        cold: bool,
1772    ) -> Result<(Vec<Change>, bool), ExecError> {
1773        let input = self.prepared.plan.nodes[id].inputs[slot];
1774        let rebuild = cold
1775            || self.rebuilt_of(up, input)
1776            || f.captures.iter().any(|&c| self.changed_of(up, c));
1777        let changes = self.feed(up, id, slot, input, rebuild)?;
1778        Ok((changes, rebuild))
1779    }
1780
1781    /// Changes at one input, whether it is an arrangement or a plain list.
1782    fn feed(
1783        &mut self,
1784        up: Option<Upstream<'_>>,
1785        id: OpId,
1786        slot: usize,
1787        input: OpId,
1788        whole: bool,
1789    ) -> Result<Vec<Change>, ExecError> {
1790        let is_arr = matches!(self.out_of(up, input)?, Out::Arr(_));
1791        if is_arr && !whole {
1792            return Ok(if self.changed_of(up, input) {
1793                self.changes_of(up, input)
1794            } else {
1795                Vec::new()
1796            });
1797        }
1798        if is_arr {
1799            let changes: Vec<Change> = match self.out_of(up, input)? {
1800                Out::Arr(a) => a
1801                    .entries
1802                    .iter()
1803                    .map(|(k, v)| Change {
1804                        key: k.clone(),
1805                        old: None,
1806                        new: Some(v.clone()),
1807                    })
1808                    .collect(),
1809                Out::Val(_) => Vec::new(),
1810            };
1811            while self.cells[id].shadow.len() <= slot {
1812                self.cells[id].shadow.push(BTreeMap::new());
1813            }
1814            self.cells[id].shadow[slot].clear();
1815            return Ok(changes);
1816        }
1817        // A plain list: no deltas of its own, so this operator makes them by comparing against the
1818        // copy it last saw. `O(n)` in the list's length — which is the honest cost of a collection
1819        // that arrived from a `match` or an `if` rather than from an arrangement.
1820        if !whole && !self.changed_of(up, input) {
1821            return Ok(Vec::new());
1822        }
1823        let value = self.materialise(up, input)?;
1824        let next: BTreeMap<Key, Value> = list_entries(&value).into_iter().collect();
1825        while self.cells[id].shadow.len() <= slot {
1826            self.cells[id].shadow.push(BTreeMap::new());
1827        }
1828        if whole {
1829            self.cells[id].shadow[slot].clear();
1830        }
1831        // Diff before storing, so `next` moves into the shadow rather than being cloned into it.
1832        let prev = &self.cells[id].shadow[slot];
1833        let mut changes = Vec::new();
1834        for (key, v) in &next {
1835            match prev.get(key) {
1836                Some(before) if before == v => {}
1837                before => changes.push(Change {
1838                    key: key.clone(),
1839                    old: before.cloned(),
1840                    new: Some(v.clone()),
1841                }),
1842            }
1843        }
1844        for (key, before) in prev {
1845            if !next.contains_key(key) {
1846                changes.push(Change {
1847                    key: key.clone(),
1848                    old: Some(before.clone()),
1849                    new: None,
1850                });
1851            }
1852        }
1853        changes.sort_by(|a, b| a.key.cmp(&b.key));
1854        self.cells[id].shadow[slot] = next;
1855        Ok(changes)
1856    }
1857
1858    fn take_arrangement(&mut self, id: OpId, rebuild: bool) -> Arrangement {
1859        if rebuild {
1860            self.cells[id].positions.clear();
1861            return Arrangement::default();
1862        }
1863        match std::mem::take(&mut self.cells[id].out) {
1864            Out::Arr(a) => a,
1865            Out::Val(_) => Arrangement::default(),
1866        }
1867    }
1868
1869    fn publish(&mut self, id: OpId, mut arr: Arrangement, changes: Vec<Change>, rebuilt: bool) {
1870        self.work.touched += changes.len() as u64;
1871        arr.touch();
1872        self.cells[id].changed = !changes.is_empty() || rebuilt;
1873        self.cells[id].changes = changes;
1874        self.cells[id].rebuilt = rebuilt;
1875        self.cells[id].out = Out::Arr(arr);
1876    }
1877
1878    /// The whole collection as inserts — the path taken when an operator cannot derive a delta.
1879    fn replace(&mut self, id: OpId, entries: Vec<(Key, Value)>) -> Result<(), ExecError> {
1880        let mut arr = Arrangement::default();
1881        let mut changes = Vec::new();
1882        for (key, v) in entries {
1883            arr.entries.insert(key.clone(), v.clone());
1884            changes.push(Change {
1885                key,
1886                old: None,
1887                new: Some(v),
1888            });
1889        }
1890        self.publish(id, arr, changes, true);
1891        Ok(())
1892    }
1893
1894    fn fun_of(&self, id: OpId) -> Result<Callable, ExecError> {
1895        self.nth_fun_of(id, 0)
1896    }
1897
1898    /// One of an operator's prepared functions, in [`crate::plan::Op::funs`]' order.
1899    fn nth_fun_of(&self, id: OpId, n: usize) -> Result<Callable, ExecError> {
1900        self.prepared.funs[id].get(n).cloned().ok_or_else(|| {
1901            ExecError::new("a collection operator has no prepared function", Span::NONE)
1902        })
1903    }
1904
1905    fn captures(&mut self, up: Option<Upstream<'_>>, f: &Fun) -> Result<Vec<Value>, ExecError> {
1906        let mut out = Vec::with_capacity(f.captures.len());
1907        for &c in &f.captures {
1908            out.push(self.materialise(up, c)?);
1909        }
1910        Ok(out)
1911    }
1912
1913    /// The value of a node, building the list an arrangement stands for if a consumer needs it.
1914    ///
1915    /// This is where the remaining `O(n)` lives, and naming it is the point: assembling `n`
1916    /// elements into a `Value::List` for a pointwise consumer copies `n` handles per event even
1917    /// when one of them moved. What it does *not* do is re-derive the elements — those came from
1918    /// the arrangement, and only the changed ones were computed.
1919    ///
1920    /// For a *shared* arrangement it is also copied only once between every subscriber, because the
1921    /// cache lives beside the arrangement rather than in the engine that asked.
1922    fn materialise(&mut self, up: Option<Upstream<'_>>, id: OpId) -> Result<Value, ExecError> {
1923        let (listed, n) = match self.out_of(up, id)? {
1924            Out::Val(v) => return Ok(v.clone()),
1925            Out::Arr(a) => a.listed_value(),
1926        };
1927        self.work.materialised += n;
1928        Ok(listed)
1929    }
1930
1931    // ---------------------------------------------------------------------------------------
1932    // Reading a node this engine may not own
1933    // ---------------------------------------------------------------------------------------
1934
1935    /// A node's output, from this engine's own cells or from the shared dataflow above it.
1936    fn out_of<'e>(&'e self, up: Option<Upstream<'e>>, id: OpId) -> Result<&'e Out, ExecError> {
1937        if self.owns(id) {
1938            return Ok(&self.cells[id].out);
1939        }
1940        match up {
1941            Some(u) => Ok(u.out(id)),
1942            None => Err(missing_upstream(id)),
1943        }
1944    }
1945
1946    /// Whether a node moved since this engine last looked at it.
1947    ///
1948    /// For an upstream node that is "since the version this subscriber last rendered", not "at the
1949    /// latest version" — a subscriber that skipped three events has to see all three, or an
1950    /// operator below it would keep an entry the shared side has already withdrawn.
1951    fn changed_of(&self, up: Option<Upstream<'_>>, id: OpId) -> bool {
1952        if self.owns(id) {
1953            return self.cells[id].changed;
1954        }
1955        // No upstream where one is needed is an error the caller will raise when it reads the
1956        // value; answering "changed" here keeps it on the path that does.
1957        up.map(|u| u.changed(id)).unwrap_or(true)
1958    }
1959
1960    fn rebuilt_of(&self, up: Option<Upstream<'_>>, id: OpId) -> bool {
1961        if self.owns(id) {
1962            return self.cells[id].rebuilt;
1963        }
1964        up.map(|u| u.rebuilt(id)).unwrap_or(true)
1965    }
1966
1967    fn changes_of(&self, up: Option<Upstream<'_>>, id: OpId) -> Vec<Change> {
1968        if self.owns(id) {
1969            return self.cells[id].changes.clone();
1970        }
1971        up.map(|u| u.changes(id)).unwrap_or_default()
1972    }
1973}
1974
1975fn missing_upstream(id: OpId) -> ExecError {
1976    ExecError::new(
1977        format!("operator {id} belongs to the shared dataflow, and none was supplied"),
1978        Span::NONE,
1979    )
1980}
1981
1982// -------------------------------------------------------------------------------------------
1983// The shared dataflow (§5.3)
1984// -------------------------------------------------------------------------------------------
1985
1986/// What the shared dataflow did in advancing from one state version to the next.
1987///
1988/// A subscriber renders when it is woken, not when the fold moves, so it can be several versions
1989/// behind by the time it looks. Its per-session operators need every change since *its* last
1990/// render, not the latest one — an entry withdrawn at version 8 and never mentioned again would
1991/// otherwise survive in a subscriber that last rendered at version 7 and next renders at 9.
1992///
1993/// A rebuilt operator's changes are deliberately **not** kept: a consumer downstream of a rebuild
1994/// re-reads the whole arrangement instead of applying changes, so storing them would retain a copy
1995/// of the collection per remembered version for nothing.
1996struct Step {
1997    from: u64,
1998    to: u64,
1999    changed: BTreeSet<OpId>,
2000    rebuilt: BTreeSet<OpId>,
2001    changes: BTreeMap<OpId, Arc<[Change]>>,
2002}
2003
2004/// A reader's frontier before it has rendered anything.
2005///
2006/// It constrains nothing: a reader with no arrangements rebuilds from the current ones whatever
2007/// history is kept, so treating it as a frontier of 0 would retain the maximum for the one reader
2008/// that cannot use a single step of it. `u64::MAX` falls out of the minimum instead of having to be
2009/// filtered out of it.
2010const UNRENDERED: u64 = u64::MAX;
2011
2012type ReaderId = u64;
2013
2014struct SharedInner {
2015    engine: Engine,
2016    version: u64,
2017    /// Whether the shared prefix has been computed at all.
2018    ///
2019    /// Separate from `version` because a freshly recovered application is at version 0 with a real
2020    /// accumulator behind it — an empty log is a state, not the absence of one — so "already at the
2021    /// version you asked for" and "never advanced" are different facts and only one of them means
2022    /// there is nothing to do.
2023    started: bool,
2024    /// Oldest first, and contiguous: `history[k].to == history[k + 1].from`.
2025    history: VecDeque<Step>,
2026    /// Every attached subscriber, and how far behind it is.
2027    ///
2028    /// The set is what decides whether the arrangements are worth holding at all; the frontiers are
2029    /// what decide how much of the change history is. Both are read only under this lock, and the
2030    /// frontiers are *written* outside it — see [`Attachment`].
2031    readers: BTreeMap<ReaderId, Arc<AtomicU64>>,
2032}
2033
2034impl SharedInner {
2035    /// The oldest version any attached reader can still ask for changes since.
2036    ///
2037    /// A step whose `to` is at or below this is retained by nobody: every reader has already
2038    /// rendered past it. With no readers at all it is the current version, so everything is
2039    /// droppable — which is the same fact the release path acts on more thoroughly.
2040    fn floor(&self) -> u64 {
2041        self.readers
2042            .values()
2043            .map(|f| f.load(Ordering::Relaxed))
2044            .min()
2045            .unwrap_or(UNRENDERED)
2046            .min(self.version)
2047    }
2048
2049    /// Drop the steps no attached reader can still ask for, and cap what is left.
2050    ///
2051    /// Two bounds, and they are different kinds of thing. The floor is a *fact*: a step below it is
2052    /// retained for nobody. The depth is a *policy*: past it we would rather a very late subscriber
2053    /// rebuild than hold change history for it indefinitely.
2054    fn compact(&mut self, depth: usize) {
2055        let floor = self.floor();
2056        while self.history.front().is_some_and(|s| s.to <= floor) {
2057            self.history.pop_front();
2058        }
2059        while self.history.len() > depth {
2060            self.history.pop_front();
2061        }
2062    }
2063}
2064
2065/// How long a shared dataflow keeps what a subscriber might still ask for.
2066///
2067/// [`docs/23-incremental-views-report.md`](../../../../../docs/23-incremental-views-report.md)
2068/// §23.19 recorded both of these as constants that should have been policies: the history was 64
2069/// versions "because a subscriber further behind than that is not the bottleneck", and the
2070/// arrangements were never dropped at all.
2071#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2072pub struct Retention {
2073    /// The **ceiling** on retained change history, in versions. The reader frontiers are the floor,
2074    /// and they are usually far lower — this bounds what one subscriber that has stopped rendering
2075    /// can pin.
2076    pub depth: usize,
2077    /// Whether to give up the arrangements when the last subscriber goes.
2078    ///
2079    /// On, the process holds nothing between fanouts and the next subscriber pays a cold start. Off,
2080    /// they stay warm for a reconnection that may not come. The trade is a real one and it belongs
2081    /// to a deployment rather than to this file, which is why it is here and not a `const`.
2082    pub release_when_idle: bool,
2083}
2084
2085/// How many versions of change history a shared dataflow keeps **at most**.
2086///
2087/// The cost is one `Change` per entry that moved per remembered version — a delta, not a
2088/// collection, because a rebuilt operator's changes are not kept. The benefit is that a subscriber
2089/// this many events behind still updates by delta rather than rebuilding. 64 is well past the point
2090/// where a subscriber that far behind is the bottleneck.
2091///
2092/// It is a ceiling rather than the retention itself: what is actually kept is bounded below by the
2093/// oldest reader's frontier, which on a fanout of subscribers that all render is one step.
2094const HISTORY: usize = 64;
2095
2096impl Default for Retention {
2097    fn default() -> Retention {
2098        Retention {
2099            depth: HISTORY,
2100            release_when_idle: true,
2101        }
2102    }
2103}
2104
2105/// The operators of a plan that do not read the session, arranged **once** for every subscriber.
2106///
2107/// "Do not read the session" is the sentence §5.3 uses and it is one atom short: what this holds is
2108/// the operators that are a function of the accumulator alone, so everything downstream of
2109/// [`crate::plan::Op::Presence`] is excluded too. The reason is this type's `version` — it is the
2110/// log's `seq`, and a roster moves when `seq` does not
2111/// ([`docs/48`](../../../../../docs/48-identity-report.md) §48.9).
2112///
2113/// [`docs/05-tier-lowering.md`](../../../../../docs/05-tier-lowering.md) §5.3:
2114///
2115/// > a thousand connected users of `todos.map(filter_by(session.user))` must compile to *one*
2116/// > shared dataflow whose final per-session operators (filter, project, diff) run per subscriber
2117///
2118/// [`crate::plan::Plan`] has said which nodes those are since the plan existed — `per_session` is
2119/// false for exactly the operators reachable from the accumulator without passing through the
2120/// session. What was missing was somewhere for them to live that is not one subscriber's engine.
2121///
2122/// # The three choices §23.14 said this design had in it
2123///
2124/// 1. **Who advances it.** Not the sequencer: that would put view maintenance on the write path and
2125///    do it for a state nobody is looking at. The *first subscriber to render at a new version*
2126///    advances it, under a write lock, and every subscriber that renders at that version afterwards
2127///    finds it done. So the work happens once per version, is paid by a renderer that was about to
2128///    do it anyway, and does not happen at all when nobody is subscribed.
2129/// 2. **What a subscriber holds while it renders.** A read lock, for the whole of its own render.
2130///    Readers do not block readers, so a thousand subscribers render concurrently; the only writer
2131///    is the advance, which is `O(δ)`. The alternative — publishing an immutable snapshot per
2132///    version — has to copy any arrangement that moved, which is the `O(n)` this engine exists to
2133///    remove.
2134/// 3. **What happens to a subscriber that fell behind.** It replays the changes it missed, from a
2135///    bounded history of recent versions (`Step`). Beyond that history it rebuilds — correct at
2136///    any lag, because a rebuild reads the current arrangement whole and a rebuild is already
2137///    contagious downstream (`Cell::rebuilt`).
2138///
2139/// # What is still not shared
2140///
2141/// The *page* is per-session in every corpus program, so what is shared is the prefix below the
2142/// session, not the render. `24-feed.beck` is the case where that prefix is most of the plan and
2143/// the sketch is the case where it is least; `docs/23` has the table.
2144///
2145/// # The lifecycle: who keeps this alive, and for how long
2146///
2147/// The three choices above say how the dataflow is *maintained*. They are silent about when it
2148/// stops being worth maintaining, which [`docs/23`](../../../../../docs/23-incremental-views-report.md)
2149/// §23.19 recorded as two loose ends — arrangements that are never released, and a change history
2150/// that is a constant rather than a policy. Both are the same missing rule, and it is the
2151/// reader-frontier discipline of differential dataflow's shared arrangements: a reader set, a
2152/// frontier per reader, history compactable up to the minimum frontier, and the trace droppable
2153/// when the reader set is empty.
2154///
2155/// So a subscriber engine is **counted**. [`SharedDataflow::subscriber`] enters it in the reader
2156/// set and its `Drop` removes it; each render publishes the version it reached; an advance
2157/// compacts to the oldest frontier and, when the last reader goes, the arrangements are released
2158/// outright. What the process holds is then a function of who is connected rather than of what has
2159/// ever connected.
2160pub struct SharedDataflow {
2161    inner: RwLock<SharedInner>,
2162    retention: Retention,
2163    /// How many times the shared prefix has actually been advanced.
2164    ///
2165    /// The metric the whole design turns on: a thousand subscribers rendering at one version must
2166    /// advance it *once*, and a counter is how that is a test rather than a claim.
2167    advances: AtomicU64,
2168    /// How many times the arrangements have been given up because nobody was reading them.
2169    releases: AtomicU64,
2170    next_reader: AtomicU64,
2171}
2172
2173impl SharedDataflow {
2174    pub fn new(prepared: Arc<Prepared>) -> SharedDataflow {
2175        SharedDataflow::with_retention(prepared, Retention::default())
2176    }
2177
2178    pub fn with_retention(prepared: Arc<Prepared>, retention: Retention) -> SharedDataflow {
2179        let owns: Arc<[bool]> = prepared.plan.nodes.iter().map(|n| !n.per_session).collect();
2180        SharedDataflow {
2181            inner: RwLock::new(SharedInner {
2182                engine: Engine::for_nodes(prepared, owns),
2183                version: 0,
2184                started: false,
2185                history: VecDeque::new(),
2186                readers: BTreeMap::new(),
2187            }),
2188            retention,
2189            advances: AtomicU64::new(0),
2190            releases: AtomicU64::new(0),
2191            next_reader: AtomicU64::new(0),
2192        }
2193    }
2194
2195    pub fn retention(&self) -> Retention {
2196        self.retention
2197    }
2198
2199    /// A subscriber's engine over the same plan: the per-session operators, and nothing else.
2200    ///
2201    /// The engine is a **reader** of this dataflow for exactly as long as it lives. It takes an
2202    /// `Arc<Self>` because that is what makes the second half true: the engine has to be able to
2203    /// say it has gone, and a subscription ends by dropping its engine rather than by calling
2204    /// anything.
2205    pub fn subscriber(self: &Arc<Self>) -> Engine {
2206        let mut inner = self.write();
2207        let mut engine = Engine::subscriber(inner.engine.prepared.clone());
2208        let id = self.next_reader.fetch_add(1, Ordering::Relaxed);
2209        let frontier = Arc::new(AtomicU64::new(UNRENDERED));
2210        inner.readers.insert(id, frontier.clone());
2211        engine.attached = Some(Attachment {
2212            shared: self.clone(),
2213            id,
2214            frontier,
2215        });
2216        engine
2217    }
2218
2219    /// A reader of the shared arrangements that renders no page: [`crate::read`]'s SQL client.
2220    ///
2221    /// It is a member of the same reader set as a subscription, and that is the design rather than
2222    /// an implementation convenience. A SQL client holding a connection is a reason to keep the
2223    /// arrangements — it is going to ask again — and a SQL client that has gone is not, which is
2224    /// exactly what the reader set already decides for subscribers
2225    /// ([`docs/23`](../../../../../docs/23-incremental-views-report.md)). The alternative, reading the
2226    /// arrangements without joining the set, has a release racing every query.
2227    ///
2228    /// Its frontier stays at the unrendered one: a reader that never applies a delta cannot use the
2229    /// change history, so pinning any of it for this reader would retain history nobody reads.
2230    pub fn reader(self: &Arc<Self>) -> Reader {
2231        let mut inner = self.write();
2232        let id = self.next_reader.fetch_add(1, Ordering::Relaxed);
2233        let frontier = Arc::new(AtomicU64::new(UNRENDERED));
2234        inner.readers.insert(id, frontier);
2235        Reader {
2236            shared: self.clone(),
2237            id,
2238        }
2239    }
2240
2241    /// A subscriber has gone. Drop what only it could still have asked for.
2242    ///
2243    /// Called from [`Engine`]'s `Drop`, so it must not be reachable while this thread holds either
2244    /// guard — it is not: the engine a `SharedInner` owns is built by `Engine::for_nodes` and is
2245    /// never a reader of anything.
2246    fn detach(&self, id: ReaderId) {
2247        let mut inner = self.write();
2248        inner.readers.remove(&id);
2249        if inner.readers.is_empty() && self.retention.release_when_idle {
2250            self.release(&mut inner);
2251        } else {
2252            let depth = self.retention.depth;
2253            inner.compact(depth);
2254        }
2255    }
2256
2257    /// Give up the arrangements. Nobody is reading them and the accumulator they came from remains,
2258    /// so this costs the next subscriber a cold start and costs correctness nothing.
2259    ///
2260    /// Deliberately the same reset the error path takes, and for the same reason: what is left has
2261    /// to be a dataflow that says it has never been advanced, rather than one that has been
2262    /// advanced and then hollowed out.
2263    fn release(&self, inner: &mut SharedInner) {
2264        if !inner.started {
2265            return;
2266        }
2267        inner.engine.reset();
2268        inner.history.clear();
2269        inner.started = false;
2270        inner.version = 0;
2271        self.releases.fetch_add(1, Ordering::Relaxed);
2272    }
2273
2274    /// Render one subscriber's page, maintaining the shared prefix once for all of them.
2275    ///
2276    /// `version` identifies the state: two calls with the same `version` must pass the same
2277    /// `state`, because the second is served from what the first computed. Returns the page and the
2278    /// version it actually reflects, which may be **newer** than the one asked for — another
2279    /// subscriber may have advanced the shared side in between, and rendering the newer state is
2280    /// correct where rendering the older one would mean unwinding an arrangement.
2281    ///
2282    /// That returned version is not a courtesy. A patch frame is labelled with a `seq` and a
2283    /// resuming client is served the difference from it (§4.3), so a frame labelled with a state
2284    /// the page does not reflect is a wrong DOM after the next reconnect.
2285    pub fn render(
2286        &self,
2287        engine: &mut Engine,
2288        state: &Value,
2289        version: u64,
2290        session: &Value,
2291        presence: &Value,
2292    ) -> Result<(Value, u64), ExecError> {
2293        self.render_all(
2294            engine,
2295            state,
2296            version,
2297            session,
2298            presence,
2299            &crate::edge::no_awareness(),
2300        )
2301    }
2302
2303    /// The same render, against both rosters the caller may be keeping.
2304    ///
2305    /// [`SharedDataflow::render`] passes an empty awareness roster, which is what a caller with no
2306    /// connection registry holds; a program whose page reads `awareness` is rendered through here.
2307    #[allow(clippy::too_many_arguments)]
2308    pub fn render_all(
2309        &self,
2310        engine: &mut Engine,
2311        state: &Value,
2312        version: u64,
2313        session: &Value,
2314        presence: &Value,
2315        aware: &Value,
2316    ) -> Result<(Value, u64), ExecError> {
2317        self.advance(state, version)?;
2318        let inner = self.read();
2319        let up = Upstream::new(&inner, engine.seen);
2320        let page = engine.render_from(Some(up), state, session, presence, aware)?;
2321        engine.seen = inner.version;
2322        // Published outside this dataflow's write lock, and this is the whole reason a frontier is
2323        // an atomic: a render must not serialise against the other renders it is concurrent with.
2324        // Publishing it *after* the render is what makes it safe to compact against — a reader
2325        // whose frontier still reads older than it is retains more history than it needs, and a
2326        // reader that retains too little is the only way this could be wrong.
2327        if let Some(a) = &engine.attached {
2328            a.frontier.store(inner.version, Ordering::Relaxed);
2329        }
2330        Ok((page, inner.version))
2331    }
2332
2333    /// Bring the shared prefix up to `version`, if some other subscriber has not already.
2334    fn advance(&self, state: &Value, version: u64) -> Result<(), ExecError> {
2335        {
2336            let inner = self.read();
2337            if inner.started && inner.version >= version {
2338                return Ok(());
2339            }
2340        }
2341        let mut inner = self.write();
2342        // Checked again under the write lock: between the read above and here, another subscriber
2343        // may have done exactly this.
2344        if inner.started && inner.version >= version {
2345            return Ok(());
2346        }
2347        let from = inner.version;
2348        if let Err(e) = inner.engine.advance(state) {
2349            // The engine has already discarded its arrangements. The history describes a dataflow
2350            // that no longer exists, so it goes too, and every subscriber rebuilds.
2351            inner.history.clear();
2352            inner.started = false;
2353            inner.version = 0;
2354            return Err(e);
2355        }
2356        inner.started = true;
2357        self.advances.fetch_add(1, Ordering::Relaxed);
2358        let step = inner.engine.step(from, version);
2359        inner.history.push_back(step);
2360        inner.version = version;
2361        // Under the same write lock as the advance, so nothing is compacted away between a
2362        // subscriber deciding what it needs and reading it: a render holds the read lock for its
2363        // whole duration, and this cannot run until every render in flight has finished.
2364        let depth = self.retention.depth;
2365        inner.compact(depth);
2366        Ok(())
2367    }
2368
2369    /// The version the shared prefix currently reflects.
2370    pub fn version(&self) -> u64 {
2371        self.read().version
2372    }
2373
2374    /// How many times the shared prefix has been advanced since the process started.
2375    ///
2376    /// §5.3's claim is that a thousand subscribers of one view share one dataflow. This is the
2377    /// number that says so: it counts advances, not renders, so it stays flat as subscribers are
2378    /// added and moves only when the fold does.
2379    pub fn advances(&self) -> u64 {
2380        self.advances.load(Ordering::Relaxed)
2381    }
2382
2383    /// How many times the arrangements have been given up because nobody was reading them.
2384    ///
2385    /// The counterpart to [`SharedDataflow::advances`], and the number a deployment weighs against
2386    /// it: every release is a cold start charged to whichever subscriber reconnects first.
2387    pub fn releases(&self) -> u64 {
2388        self.releases.load(Ordering::Relaxed)
2389    }
2390
2391    /// How many subscribers are attached right now.
2392    pub fn readers(&self) -> usize {
2393        self.read().readers.len()
2394    }
2395
2396    /// How many versions of change history are being kept.
2397    ///
2398    /// Bounded above by [`Retention::depth`] and below by the oldest attached reader's frontier, so
2399    /// on a fanout whose subscribers all render at every version it is 1 rather than 64. This is
2400    /// the number that says the frontier discipline is doing something.
2401    pub fn retained(&self) -> usize {
2402        self.read().history.len()
2403    }
2404
2405    /// Entries across every shared arrangement — held once, however many subscribers there are.
2406    pub fn arranged(&self) -> u64 {
2407        self.read().engine.arranged()
2408    }
2409
2410    /// What the shared prefix retains beyond the accumulator — once, for every subscriber.
2411    pub fn footprint(&self, base: &Value) -> Footprint {
2412        self.read().engine.footprint(base)
2413    }
2414
2415    pub fn work(&self) -> Work {
2416        self.read().engine.work()
2417    }
2418
2419    fn read(&self) -> std::sync::RwLockReadGuard<'_, SharedInner> {
2420        self.inner
2421            .read()
2422            .unwrap_or_else(std::sync::PoisonError::into_inner)
2423    }
2424
2425    fn write(&self) -> std::sync::RwLockWriteGuard<'_, SharedInner> {
2426        self.inner
2427            .write()
2428            .unwrap_or_else(std::sync::PoisonError::into_inner)
2429    }
2430}
2431
2432/// A reader of a [`SharedDataflow`]'s arrangements that renders nothing.
2433///
2434/// The read model's half of §5.3's cut: the operators that do not read the session are exactly the
2435/// ones a client with no session can be shown ([`crate::read`]). Holding one keeps the arrangements
2436/// from being released; dropping it is how a SQL connection ends.
2437pub struct Reader {
2438    shared: Arc<SharedDataflow>,
2439    id: ReaderId,
2440}
2441
2442impl Drop for Reader {
2443    fn drop(&mut self) {
2444        self.shared.detach(self.id);
2445    }
2446}
2447
2448impl Reader {
2449    /// One shared operator's output, as the rows it stands for, at `version`.
2450    ///
2451    /// Advances the shared prefix first, by the same path a rendering subscriber takes — so a query
2452    /// issued after an ack sees that ack's event, and a query issued when nothing is subscribed
2453    /// pays for the advance nobody else has paid for. That is the read model's whole freshness
2454    /// story: there is no projection to lag behind.
2455    ///
2456    /// An arrangement answers its entries in key order, which is the order the plan gives it and
2457    /// therefore the order the page renders in. A value answers itself, once.
2458    pub fn read(&self, state: &Value, version: u64, id: OpId) -> Result<Vec<Value>, ExecError> {
2459        self.shared.advance(state, version)?;
2460        let inner = self.shared.read();
2461        if !inner.engine.owns.get(id).copied().unwrap_or(false) {
2462            return Err(ExecError::new(
2463                format!("operator {id} is not part of the shared dataflow"),
2464                Span::NONE,
2465            ));
2466        }
2467        Ok(match &inner.engine.cells[id].out {
2468            Out::Arr(a) => a.entries.values().cloned().collect(),
2469            Out::Val(v) => vec![v.clone()],
2470        })
2471    }
2472
2473    /// **How many rows one shared operator stands for, without building any of them.**
2474    ///
2475    /// [`Reader::read`] clones every entry, which is the honest cost of *answering* with rows. A
2476    /// `select count(*)` does not want rows: it wants the number, and an arrangement is a
2477    /// `BTreeMap` that already knows it. This is §3.8's "never a recount" reaching the SQL surface —
2478    /// the same fact [`Op::Count`] reads for `list_len`, offered to a reader that is not the plan.
2479    ///
2480    /// `None` when the operator holds a *value* rather than an arrangement: a pointwise operator's
2481    /// collection is a `Value::List` it recomputed, and how many rows that stands for is a question
2482    /// about the value rather than about the dataflow. The caller falls back to a scan and is no
2483    /// worse off than before.
2484    pub fn len(&self, state: &Value, version: u64, id: OpId) -> Result<Option<u64>, ExecError> {
2485        self.shared.advance(state, version)?;
2486        let inner = self.shared.read();
2487        if !inner.engine.owns.get(id).copied().unwrap_or(false) {
2488            return Err(ExecError::new(
2489                format!("operator {id} is not part of the shared dataflow"),
2490                Span::NONE,
2491            ));
2492        }
2493        Ok(match &inner.engine.cells[id].out {
2494            Out::Arr(a) => Some(a.entries.len() as u64),
2495            Out::Val(_) => None,
2496        })
2497    }
2498}
2499
2500impl Engine {
2501    /// What this engine's owned operators did in one advance, as a replayable step.
2502    fn step(&self, from: u64, to: u64) -> Step {
2503        let mut changed = BTreeSet::new();
2504        let mut rebuilt = BTreeSet::new();
2505        let mut changes = BTreeMap::new();
2506        for (id, cell) in self.cells.iter().enumerate() {
2507            if !self.owns[id] {
2508                continue;
2509            }
2510            if cell.changed {
2511                changed.insert(id);
2512            }
2513            if cell.rebuilt {
2514                rebuilt.insert(id);
2515            } else if !cell.changes.is_empty() {
2516                // From the slice, one copy: this runs under the shared dataflow's write lock.
2517                changes.insert(id, Arc::<[Change]>::from(cell.changes.as_slice()));
2518            }
2519        }
2520        Step {
2521            from,
2522            to,
2523            changed,
2524            rebuilt,
2525            changes,
2526        }
2527    }
2528}
2529
2530/// One subscriber's window onto the shared dataflow: its arrangements now, and everything that
2531/// moved since this subscriber last looked.
2532#[derive(Clone, Copy)]
2533struct Upstream<'a> {
2534    inner: &'a SharedInner,
2535    since: u64,
2536    /// Whether the history still covers `since`. When it does not, every upstream node reads as
2537    /// changed *and* rebuilt, so the subscriber re-reads the arrangements whole — slow, and right.
2538    resolvable: bool,
2539}
2540
2541impl<'a> Upstream<'a> {
2542    fn new(inner: &'a SharedInner, since: u64) -> Upstream<'a> {
2543        let resolvable = since == inner.version
2544            || inner
2545                .history
2546                .iter()
2547                .find(|s| s.to > since)
2548                .is_some_and(|s| s.from == since);
2549        Upstream {
2550            inner,
2551            since,
2552            resolvable,
2553        }
2554    }
2555
2556    fn out(&self, id: OpId) -> &'a Out {
2557        &self.inner.engine.cells[id].out
2558    }
2559
2560    fn window(&self) -> impl Iterator<Item = &'a Step> {
2561        let since = self.since;
2562        self.inner.history.iter().filter(move |s| s.to > since)
2563    }
2564
2565    fn changed(&self, id: OpId) -> bool {
2566        !self.resolvable || self.window().any(|s| s.changed.contains(&id))
2567    }
2568
2569    fn rebuilt(&self, id: OpId) -> bool {
2570        !self.resolvable || self.window().any(|s| s.rebuilt.contains(&id))
2571    }
2572
2573    /// Everything that moved at this node since `since`, in the order it moved.
2574    ///
2575    /// Concatenation rather than coalescing: a consumer applies changes in order, so a key that
2576    /// moved twice is applied twice and lands where the second one put it. Coalescing would save a
2577    /// consumer one application per repeat and cost a pass over the window; the window is a handful
2578    /// of deltas.
2579    fn changes(&self, id: OpId) -> Vec<Change> {
2580        self.window()
2581            .filter_map(|s| s.changes.get(&id))
2582            .flat_map(|c| c.iter().cloned())
2583            .collect()
2584    }
2585}
2586
2587/// One element of a flattened collection: the outer key, then the position within it.
2588fn inner_key(outer: &Key, i: usize) -> Key {
2589    let mut k: Vec<Value> = outer.to_vec();
2590    k.push(Value::Int(i as i64));
2591    Arc::from(k)
2592}
2593
2594/// What one row contributes to its group's total, as the accumulator holds it.
2595///
2596/// An `Int` by typing rather than by check — [`Agg::Sum`] is only ever built from a `list_sum`,
2597/// whose argument is a `list[Int]` — so this is a seam a wrong plan would come through, and it
2598/// answers with the failure the site would have raised rather than with a panic inside a render.
2599fn contribution(v: &Value) -> Result<i128, ExecError> {
2600    v.as_int()
2601        .map(i128::from)
2602        .ok_or_else(|| ExecError::new("`list_sum` expects a list of Ints", Span::NONE))
2603}
2604
2605/// What `list_sum` raises, in the words the interpreter's own `list_sum` uses, because the
2606/// maintained plan and the recompute are held to the same failure and not merely to the same
2607/// answer.
2608///
2609/// The accumulator overflowing is a different event from the answer not fitting, and it is
2610/// unreachable rather than defensive: a group would have to hold about `2^64` rows for a sum of
2611/// `Int`s to leave `i128`. It is written as a failure anyway because the alternative is `+=`, which
2612/// **panics in a debug build and wraps in a release one** — `docs/93` §93.3's defect, whose whole
2613/// point was that which programs run must not depend on how the compiler was built.
2614fn sum_overflowed() -> ExecError {
2615    ExecError::new("`list_sum` overflowed", Span::NONE)
2616}
2617
2618/// A join key, as a key of the index.
2619///
2620/// One component, which is the whole of a unique index's key and the **prefix** of a grouped one's
2621/// — so the same value is a point lookup in the first and the start of a range in the second.
2622fn key_of(jk: &Value) -> Key {
2623    Arc::from(vec![jk.clone()])
2624}
2625
2626/// One joined row: the left value, and what it matched.
2627///
2628/// The right half is whatever the expression this operator replaced evaluated to — an `Option` for
2629/// a `map_get`, a `list` for a `filter_list` — which [`Engine::answer`] has already built. A join
2630/// that dropped unmatched rows would be a different operator and a different page.
2631fn joined(left: Value, right: Value) -> Value {
2632    Value::record(
2633        crate::relate::ROW,
2634        None,
2635        [(crate::relate::LEFT, left), (crate::relate::RIGHT, right)],
2636    )
2637}
2638
2639/// Forget that a left row was waiting on a join key, and forget the key when nobody is left.
2640///
2641/// The second half is not tidiness: without it the reverse index grows by one entry per key that
2642/// has ever been joined on and never shrinks, which is a leak that a shape gate over a *collection*
2643/// would not see because it is proportional to the log rather than to the rows.
2644fn withdraw(back: &mut BTreeMap<Value, BTreeSet<Key>>, jk: &Value, lk: &Key) {
2645    if let Some(waiting) = back.get_mut(jk) {
2646        waiting.remove(lk);
2647        if waiting.is_empty() {
2648            back.remove(jk);
2649        }
2650    }
2651}
2652
2653/// A list, as an arrangement keyed by position.
2654fn list_entries(v: &Value) -> Vec<(Key, Value)> {
2655    match v {
2656        Value::List(xs) => xs
2657            .iter()
2658            .enumerate()
2659            .map(|(i, x)| (Arc::from(vec![Value::Int(i as i64)]), x.clone()))
2660            .collect(),
2661        Value::Map(m) => m
2662            .iter()
2663            .map(|(k, v)| (Arc::from(vec![k.clone()]), v.clone()))
2664            .collect(),
2665        _ => Vec::new(),
2666    }
2667}
2668
2669/// A conservative "did this value move" test: `true` only when it certainly did not.
2670///
2671/// Structural equality would be `O(size)`, and doing it once per operator per event would put back
2672/// the cost this engine removes. Collections and rendered trees therefore compare by *pointer*: two
2673/// equal-but-separately-built lists answer `false`, which costs one recompute that the old runtime
2674/// performed unconditionally. Records compare field by field, because that is how a program's own
2675/// small values — a `Summary`, a `Tally` — are built, and the whole point of a plan is that an
2676/// event which does not move the summary does not re-render the page below it.
2677// Not `pub`: this is a *conservative* changed-test — `Arc::ptr_eq` for lists and Html — and a
2678// caller reading it as equality would be misled.
2679fn same(a: &Value, b: &Value) -> bool {
2680    match (a, b) {
2681        (Value::Unit, Value::Unit) => true,
2682        (Value::Bool(x), Value::Bool(y)) => x == y,
2683        (Value::Int(x), Value::Int(y)) => x == y,
2684        (Value::Float(x), Value::Float(y)) => x == y,
2685        (Value::Str(x), Value::Str(y)) => x == y,
2686        (Value::List(x), Value::List(y)) => Arc::ptr_eq(x, y),
2687        (Value::Map(x), Value::Map(y)) => x.same_root(y),
2688        (Value::Html(x), Value::Html(y)) => Arc::ptr_eq(x, y),
2689        (Value::Attr(x), Value::Attr(y)) => Arc::ptr_eq(x, y),
2690        (Value::Data(a), Value::Data(b)) => {
2691            // One pointer now compares the whole record, where three fields used to be compared
2692            // one at a time — the shape `Value::Data(Arc<Record>)` was chosen for.
2693            if Arc::ptr_eq(a, b) {
2694                return true;
2695            }
2696            let (t1, v1, f1) = (&a.ty, &a.variant, &a.fields);
2697            let (t2, v2, f2) = (&b.ty, &b.variant, &b.fields);
2698            t1 == t2
2699                && v1 == v2
2700                && f1.len() == f2.len()
2701                && f1
2702                    .iter()
2703                    .zip(f2.iter())
2704                    .all(|((n1, x), (n2, y))| n1 == n2 && same(x, y))
2705        }
2706        _ => false,
2707    }
2708}
2709
2710// -------------------------------------------------------------------------------------------
2711// Footprint
2712// -------------------------------------------------------------------------------------------
2713
2714/// A deterministic byte estimate for the memory a subscription's engine retains.
2715///
2716/// `docs/05-tier-lowering.md` §5.3 names per-session memory as one of three metrics to export,
2717/// and Phase 0's kill gate is written in kilobytes per idle session
2718/// (`docs/18-phase-0-report.md` §18.3). An engine per subscription is a memory-for-time trade,
2719/// so the number has to exist.
2720///
2721/// It is computed rather than sampled. A resident-set reading moves with the allocator's arena and
2722/// swung by 2× between runs of the same measurement; a counting allocator would be exact and needs
2723/// `unsafe`, which this workspace forbids. So this walks what is actually retained and adds up
2724/// `size_of` plus the bytes behind each allocation, **counting shared structure once**: a `Todo` an
2725/// arrangement holds is the same `Arc` the accumulator holds, and charging a subscription for it
2726/// would be the difference between "a handle per row" and "a row per row".
2727///
2728/// What it excludes, and therefore under-reports: allocator overhead per allocation, which for many
2729/// small allocations is substantial. It is a floor on the true cost, not a ceiling.
2730#[derive(Clone, Copy, Debug, Default)]
2731pub struct Footprint {
2732    /// Bytes retained by this engine's cells, arrangements and keys.
2733    pub bytes: u64,
2734    /// Of those, the ones in arrangements that do not read the session — what §5.3 says a thousand
2735    /// subscribers should hold once between them, and this engine holds once each.
2736    pub shared_bytes: u64,
2737    pub entries: u64,
2738}
2739
2740impl Engine {
2741    /// What this subscription retains **beyond** the accumulator it renders from.
2742    ///
2743    /// `base` is that accumulator, and walking it first is not a detail: an arrangement over
2744    /// `map_values(s.todos)` holds the *same* `Todo` records the fold holds, by `Arc`, so charging
2745    /// a subscription for them would report a row per row where the truth is a handle per row. What
2746    /// remains after the exclusion is what a thousand subscribers actually multiply.
2747    ///
2748    /// See [`Footprint`] for what the number does and does not include.
2749    pub fn footprint(&self, base: &Value) -> Footprint {
2750        let mut seen = BTreeSet::new();
2751        value_bytes(base, &mut seen);
2752        let mut acc = Footprint::default();
2753        self.footprint_into(&mut seen, &mut acc);
2754        acc
2755    }
2756
2757    /// The same walk, against an exclusion set some other engine has already contributed to.
2758    ///
2759    /// Separate from [`Engine::footprint`] because summing per-engine footprints across a fanout
2760    /// over-reports, and over-reports **exactly the thing this work is about**: with a shared
2761    /// dataflow, two subscribers' pages hold the same `ul` by `Arc`, and charging both of them for
2762    /// it would report the sharing as costing what it saves.
2763    fn footprint_into(&self, seen: &mut BTreeSet<usize>, acc: &mut Footprint) {
2764        let (mut bytes, mut shared_bytes, mut entries) = (0u64, 0u64, 0u64);
2765        for (i, cell) in self.cells.iter().enumerate() {
2766            // An operator this engine does not own costs it nothing: the shared dataflow holds it,
2767            // and `SharedDataflow::footprint` is where it is charged — once, not once per
2768            // subscriber, which is the whole point of the split.
2769            if !self.owns[i] {
2770                continue;
2771            }
2772            let mut here = std::mem::size_of::<Cell>() as u64;
2773            match &cell.out {
2774                Out::Val(v) => here += value_bytes(v, seen),
2775                Out::Arr(a) => {
2776                    entries += a.entries.len() as u64;
2777                    for (k, v) in &a.entries {
2778                        // A `BTreeMap` node holds up to 11 entries plus links; charged per entry as
2779                        // the pair plus a share of the node.
2780                        here +=
2781                            (std::mem::size_of::<Key>() + std::mem::size_of::<Value>() + 24) as u64;
2782                        here += k.len() as u64 * std::mem::size_of::<Value>() as u64;
2783                        here += value_bytes(v, seen);
2784                    }
2785                    if let Some(listed) = a.listed.get() {
2786                        here += value_bytes(listed, seen);
2787                    }
2788                }
2789            }
2790            for (k, v) in &cell.positions {
2791                here += (k.len() + v.len()) as u64 * std::mem::size_of::<Value>() as u64 + 24;
2792            }
2793            bytes += here;
2794            if !self.prepared.plan.nodes[i].per_session {
2795                shared_bytes += here;
2796            }
2797        }
2798        acc.bytes += bytes;
2799        acc.shared_bytes += shared_bytes;
2800        acc.entries += entries;
2801    }
2802}
2803
2804/// What a whole fanout retains: the accumulator once, the shared dataflow once, and each
2805/// subscriber's own operators — with every shared allocation counted **exactly once across all of
2806/// them**.
2807///
2808/// Summing [`Engine::footprint`] over the subscribers is the wrong number once there is a shared
2809/// dataflow, and wrong in the direction that flatters nothing: two subscribers' pages hold the same
2810/// `ul` by `Arc`, so charging both would report sharing as costing what it saves. This is the
2811/// number a fanout estimate should be built from, and `docs/23` is where it is.
2812pub fn fanout_footprint(
2813    base: &Value,
2814    shared: Option<&SharedDataflow>,
2815    engines: &[&Engine],
2816) -> Footprint {
2817    let mut seen = BTreeSet::new();
2818    value_bytes(base, &mut seen);
2819    let mut acc = Footprint::default();
2820    if let Some(shared) = shared {
2821        shared.read().engine.footprint_into(&mut seen, &mut acc);
2822    }
2823    for engine in engines {
2824        engine.footprint_into(&mut seen, &mut acc);
2825    }
2826    acc
2827}
2828
2829/// Bytes behind a value, counting each shared allocation once.
2830fn value_bytes(v: &Value, seen: &mut std::collections::BTreeSet<usize>) -> u64 {
2831    let mut fresh = |p: usize| seen.insert(p);
2832    match v {
2833        Value::Unit | Value::Bool(_) | Value::Int(_) | Value::Float(_) => 0,
2834        Value::Str(s) => {
2835            if fresh(s.as_ptr() as usize) {
2836                s.len() as u64
2837            } else {
2838                0
2839            }
2840        }
2841        Value::List(xs) => {
2842            if !fresh(Arc::as_ptr(xs) as usize) {
2843                return 0;
2844            }
2845            // What the list itself occupies, which is the layout's own answer: half as much for a
2846            // column, and the elements below cost nothing more either way.
2847            let mut n = xs.heap_bytes() as u64;
2848            xs.for_each(|x| n += value_bytes(x, seen));
2849            n
2850        }
2851        Value::Map(m) => {
2852            let mut n = 0;
2853            for (k, v) in m.iter() {
2854                // A tree node: key, value, size and two links.
2855                n += (2 * std::mem::size_of::<Value>() + 24) as u64;
2856                n += value_bytes(k, seen) + value_bytes(v, seen);
2857            }
2858            n
2859        }
2860        Value::Data(d) => {
2861            if !fresh(Arc::as_ptr(d) as usize) {
2862                return 0;
2863            }
2864            let mut n = (d.fields.len() * (std::mem::size_of::<Value>() + 16 + 24)) as u64;
2865            for f in d.fields.values() {
2866                n += value_bytes(f, seen);
2867            }
2868            n
2869        }
2870        Value::Html(h) => {
2871            if !fresh(Arc::as_ptr(h) as usize) {
2872                return 0;
2873            }
2874            html_bytes(h)
2875        }
2876        Value::Attr(a) => {
2877            if fresh(Arc::as_ptr(a) as usize) {
2878                std::mem::size_of::<crate::core::AttrValue>() as u64
2879            } else {
2880                0
2881            }
2882        }
2883        Value::Closure(_) => 0,
2884    }
2885}
2886
2887/// The same estimate for a rendered page, so "what the engine added" has a baseline.
2888pub fn html_footprint(h: &crate::html::Html) -> u64 {
2889    html_bytes(h)
2890}
2891
2892fn html_bytes(h: &crate::html::Html) -> u64 {
2893    use crate::html::Html;
2894    match h {
2895        Html::Text { text, .. } => std::mem::size_of::<Html>() as u64 + text.len() as u64,
2896        Html::Element {
2897            tag,
2898            attrs,
2899            key,
2900            children,
2901            ..
2902        } => {
2903            let mut n = std::mem::size_of::<Html>() as u64 + tag.len() as u64;
2904            n += key.as_ref().map(|k| k.len()).unwrap_or(0) as u64;
2905            for (a, b) in attrs {
2906                n += (a.len() + b.len() + 48) as u64;
2907            }
2908            n += (children.len() * std::mem::size_of::<Html>()) as u64;
2909            for c in children {
2910                n += html_bytes(c);
2911            }
2912            n
2913        }
2914    }
2915}