Skip to main content

Module live

Module live 

Source
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 SELECT that loads an agg binding’s fold state for Mapper::seed: per coordinate (dimension columns as text, rel already quoted), the value text the fold keeps, count(value_col) and count(*) (FoldRow), then a NULL. For count no value; for sum and avg the finite values’ sum and, in groups of their own, the infinities and NaNs; for min, max and count_distinct one group per value text. One more row has NULL dimensions and, last, the statement’s snapshot.

Type Aliases§

FoldRow
One row of seed_sql: dimension names, a value’s text, how many rows with a value it stands for, and how many rows in all.