pub fn rewrite_aggregates(ast: &Ast) -> AstExpand description
Rewrite an AST for a client engine reading synced aggregate tables — the Rust twin of
TypeScript rewriteAggregates (packages/normalized/src/agg-table.ts), byte-for-byte in
its choice of table name because both call agg_table_name.
Each relationship count becomes a precomputed, source-backed singular relationship over
its synthetic table: read the server’s count with a plain join + the same scalar projection
(aggregate_precomputed), never a reduce, which would recount already-aggregated rows.
Non-aggregate relationships recurse (a nested aggregate is rewritten too); the parent’s
frame is otherwise untouched, so the view-schema slot order is preserved.
The where tree is rewritten the SAME way (PARENT-AGGREGATE-FILTER-DESIGN.md §3): a
having_count parent gate lowers to an EXISTS whose subquery CLONES the display count_as
and adds a post-aggregation HAVING. That is a relationship count like any other, so the
premise applies to it too — the client never recomputes a count, it lacks the child rows.
Left un-rewritten the gate reduces over child rows the server (rightly) does not sync, and
every parent fails it. Since agg_table_name hashes neither alias nor having, the
gate resolves to the SAME __agg_* table the display count_as already registers: the
rewrite costs no extra table and no extra rows. Its counterpart is the server pruning the
gate’s witnesses from the footprint (table_tree) — that prune is sound only BECAUSE of
this rewrite.
§Why a Rust twin exists
The client rewrite used to live only in TypeScript, which meant no wire-bearing test tier
could value-grade an aggregate: grading re-runs the oracle over the reconstructed
footprint, and the footprint deliberately holds no child rows, so a recomputed count is
always 0. Every such tier therefore ran with aggregate generation switched off — the
blind spot both having_count bugs (the wire width collision and the planner flip) sat in.
With this, a harness can reconstruct the client’s own AST and grade the answer.