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}