Module query

Module query 

Source
Expand description

The read model’s relational half: a select compiled into the plan.

docs/99-the-data-tier-means-of-combination.md §99.9 item 9:

The read-model SQL grows joins and group by by compiling into the plan, not by growing its own interpreter — which closes §23.19 and §12.5 together and keeps one code path.

§What this module is, in one sentence

It writes the Beck expression a person would have written, and hands it to crate::plan::Plan::of_query. Nothing here joins anything, groups anything or deduplicates anything: select … join … on b.k = a.k becomes the loop for a in as: for b in bs where b.k == a.k, which crate::relate already reads as an equi-join over an index, and group by g becomes the loop over list_unique that corpus/35-workload.beck writes by hand. The operators are crate::plan::Op::Join, crate::plan::Op::ArrangeBy, crate::plan::Op::GroupBy and crate::plan::Op::Distinct — the same ones, with the same delta rules, that a program’s view compiles to.

That is the whole of item 9, and it is worth naming what the alternative would have cost: a second join, a second set of aggregates and a second distinct living beside the first, agreeing by inspection rather than by construction, and a differential harness that covers one of the two.

§The shape each query compiles to

SQLThe expression
from a join b on b.k = a.kconcat_lists(map_list(a, λx. map_list(filter_list(b, λy. y.k == x.k), λy. row))) — one stage per join, left-deep, so each stage is its own map_list in the plan and therefore gets its own join and its own index
group by gmap_list(list_unique(map_list(R, λr. g(r))), λk. …), each aggregate a question about filter_list(R, λr. g(r) == k)
count(*) per grouplist_len of that filter — the join’s own tally, so no group is built
min/max/sum(c) per grouplist_min(map_list(that filter, λr. r.c)) — crate::plan::Op::GroupBy
distinctlist_unique of the projected row
an aggregate with no group bythe aggregate of the whole collection, as one row

§What a column is, and why the rows are normalised first

A table’s columns are a schema fact; a table’s element is a run-time value — a record for a collection of models, the element itself for a collection of scalars. Rather than teach the compiled expression that difference, every table’s rows go through Table::row_values first, which is the function Table::row builds a scan’s cells with. So a column is c{n} inside the plan, for every table, and the scan and the join cannot disagree about what a column is.

§What is deliberately not here

  • No cost-based ordering. The from list fixes the left-deep order, exactly as a for loop fixes it (docs/99 §99.8’s “an inferred surface postpones the solver”, arrived at from the other side: a written surface fixes the order outright, so there is still nothing for a solver to choose).
  • No outer join, because an unmatched row would need columns invented for it.
  • No having: a where narrows the rows before they are grouped, and a filter over the groups themselves would be a second predicate language.

Structs§

Compiled
A select’s relational half, compiled and ready to run.

Functions§

compile
Compile a select into a plan over the tables it names.
compile_with
The same, with Relate said out loud.
relational
Whether a select needs the plan.