Expand description
G1: map decoded pgoutput row changes onto live-engine edits.
The logical-decoding worker keeps a live [improv_engine::session::Engine]
per model. Mapper::map turns one batch of committed row changes into
the input-cell [Edit]s it makes (one per touched cell, its final value),
or says the batch cannot be applied cell by cell (Rebuild) and the
caller must reload the model: two rows on one coordinate of a binding
without agg, a value the loader would not read, or an old row whose
values were not replicated. A TRUNCATE removes every cell its
relation’s bindings feed. Dimension values with no item yet are listed
by Mapper::unknown: the caller mints them (improv.mint_items) and
adds them (Mapper::add_item) before mapping.
Aggregated bindings (improv.binding.agg) keep per-coordinate fold state
(Fold): the row counts, an exact total for sum and avg, and for
min, max and count_distinct each distinct value’s row count, so
deleting one of two rows that share a value keeps it. Mapper::seed
loads that state at a rebuild; each change then removes its old row from
its coordinate’s fold and adds its new row to its (maybe other)
coordinate’s, and each touched cell takes its fold’s value.
Values are the pgoutput text of the column, read by the measure’s type
as the loader’s ::float8/::bool/::text/timestamptz casts read the
row. A date or timestamp without a zone is taken as UTC: the sync worker
runs with TimeZone = UTC and DateStyle = ISO.
Replay: a rebuild reads the bound tables as they are now, which may
already include transactions the slot has not yet delivered. Those are
re-applied afterwards. Each change to a binding without agg is a
per-cell “remove” or “set”, so re-applying converges (the last change to
a cell wins); the one check that is not idempotent, an insert onto an
occupied coordinate, overwrites instead of rebuilding for transactions
that ended by replay_until. A fold is not idempotent (a re-applied
insert counts twice), so each aggregated binding’s seed query also
returns the snapshot it read with (Snap, pg_current_snapshot() in
the same statement), and Mapper::map skips exactly the transactions
that snapshot saw.
Structs§
- Binding
- How one input measure is bound to the columns of one relation.
- Mapper
- Maps row changes of bound relations to input-cell edits.
- Rebuild
- Why a batch cannot be applied incrementally; the caller rebuilds.
- Snap
- A Postgres snapshot (
pg_current_snapshot()): which transactions a query saw.
Enums§
- Agg
- How the rows on one coordinate fold into its cell (
improv.binding.agg).
Functions§
- seed_
sql - The
SELECTthat loads anaggbinding’s fold state forMapper::seed: per coordinate (dimension columns as text,relalready quoted), the value text the fold keeps,count(value_col)andcount(*)(FoldRow), then a NULL. Forcountno value; forsumandavgthe finite values’ sum and, in groups of their own, the infinities and NaNs; formin,maxandcount_distinctone group per value text. One more row has NULL dimensions and, last, the statement’s snapshot.