beck_core/query.rs
1//! The read model's relational half: a `select` compiled into the plan.
2//!
3//! [`docs/99-the-data-tier-means-of-combination.md`](../../../../../docs/99-the-data-tier-means-of-combination.md)
4//! §99.9 item 9:
5//!
6//! > The read-model SQL grows joins and `group by` **by compiling into the plan**, not by growing
7//! > its own interpreter — which closes §23.19 and §12.5 together and keeps one code path.
8//!
9//! # What this module is, in one sentence
10//!
11//! It writes the Beck expression a person would have written, and hands it to
12//! [`crate::plan::Plan::of_query`]. Nothing here joins anything, groups anything or deduplicates
13//! anything: `select … join … on b.k = a.k` becomes the loop `for a in as: for b in bs where
14//! b.k == a.k`, which [`crate::relate`] already reads as an equi-join over an index, and `group by
15//! g` becomes the loop over `list_unique` that `corpus/35-workload.beck` writes by hand. The
16//! operators are [`crate::plan::Op::Join`], [`crate::plan::Op::ArrangeBy`],
17//! [`crate::plan::Op::GroupBy`] and [`crate::plan::Op::Distinct`] — the same ones, with the same
18//! delta rules, that a program's view compiles to.
19//!
20//! That is the whole of item 9, and it is worth naming what the alternative would have cost: a
21//! second join, a second set of aggregates and a second `distinct` living beside the first,
22//! agreeing by inspection rather than by construction, and a differential harness that covers one
23//! of the two.
24//!
25//! # The shape each query compiles to
26//!
27//! | SQL | The expression |
28//! |---|---|
29//! | `from a join b on b.k = a.k` | `concat_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 |
30//! | `group by g` | `map_list(list_unique(map_list(R, λr. g(r))), λk. …)`, each aggregate a question about `filter_list(R, λr. g(r) == k)` |
31//! | `count(*)` per group | `list_len` of that filter — the join's own tally, so no group is built |
32//! | `min/max/sum(c)` per group | `list_min(map_list(that filter, λr. r.c))` — [`crate::plan::Op::GroupBy`] |
33//! | `distinct` | `list_unique` of the projected row |
34//! | an aggregate with no `group by` | the aggregate of the whole collection, as one row |
35//!
36//! # What a column is, and why the rows are normalised first
37//!
38//! A table's columns are a schema fact; a table's element is a run-time value — a record for a
39//! collection of models, the element itself for a collection of scalars. Rather than teach the
40//! compiled expression that difference, every table's rows go through [`Table::row_values`] first,
41//! which is the function [`Table::row`] builds a scan's cells with. So a column is `c{n}` inside
42//! the plan, for every table, and the scan and the join cannot disagree about what a column is.
43//!
44//! # What is deliberately not here
45//!
46//! * **No cost-based ordering.** The `from` list fixes the left-deep order, exactly as a `for` loop
47//! fixes it ([`docs/99`](../../../../../docs/99-the-data-tier-means-of-combination.md) §99.8's
48//! "an inferred surface postpones the solver", arrived at from the other side: a *written*
49//! surface fixes the order outright, so there is still nothing for a solver to choose).
50//! * **No outer join**, because an unmatched row would need columns invented for it.
51//! * **No `having`**: a `where` narrows the rows before they are grouped, and a filter over the
52//! groups themselves would be a second predicate language.
53
54use std::collections::BTreeSet;
55use std::sync::Arc;
56
57use beck_diag::Span;
58
59use crate::core::{Const, Core, CoreKind, Fields, Prim, Value, VarId};
60use crate::engine::{Engine, Prepared};
61use crate::plan::{Agg, Plan, Relate};
62use crate::read::{
63 self, Cell, Column, Datum, Eval, Field, Item, Name, Schema, Select, SqlError, SqlTy, Table,
64};
65use crate::ty::{Tier, Ty};
66
67/// A conjunction — the shape [`crate::read`] parses a `where` into, because `and` is the operator
68/// that lets one term be pushed into one table's scan while another is not.
69type Filter = Vec<read::Expr>;
70
71/// One record per row, as the fields of the [`CoreKind::Make`] that builds it.
72type Row = Vec<(Arc<str>, Core)>;
73
74/// A `select`'s relational half, compiled and ready to run.
75pub struct Compiled {
76 /// What the rows this produces are called, in order.
77 pub fields: Vec<Field>,
78 /// Whether those rows are already the select list's answer.
79 ///
80 /// True for a `group by` and for a `distinct`, because the operator that produced them was
81 /// compiled *from* that list — the projection happened inside the plan. False for a plain join,
82 /// whose rows carry every column of every table so that a `where` or an `order by` can name one
83 /// the select list does not.
84 pub projected: bool,
85 /// The `where` terms this did **not** apply, for the caller to apply to the rows.
86 ///
87 /// A term that names one table is pushed into that table's scan. One that spans two tables
88 /// cannot be, and is left here — except an equality between two columns, which is a join
89 /// condition written in the `where` and is moved into the `on` it means.
90 pub residual: Filter,
91 /// The tables the plan reads, in the order its state record holds them.
92 inputs: Vec<Input>,
93 plan: Plan,
94 /// Whether the plan's root is one row rather than a collection of them — an aggregate with no
95 /// `group by` is a question about the whole table, and its answer is one row.
96 single: bool,
97}
98
99/// One table the compiled plan reads.
100struct Input {
101 table: Table,
102 /// What the query called it, because a prefilter's conditions are written with that name.
103 alias: Arc<str>,
104 /// Where this table's columns start in the row the plan reads, so `c{n}` names the same column
105 /// everywhere in it.
106 base: usize,
107 prefilter: Filter,
108}
109
110impl Compiled {
111 /// The plan this query compiles to — what `beck explain` prints, and what a gate reads.
112 pub fn plan(&self) -> &Plan {
113 &self.plan
114 }
115
116 /// Read the tables, run the plan, and answer with the rows.
117 pub fn run(&self, schema: &Schema, rows: &dyn read::Rows) -> Result<Vec<Vec<Cell>>, SqlError> {
118 Ok(self.run_measured(schema, rows)?.0)
119 }
120
121 /// The same, and what the engine did to produce it.
122 ///
123 /// [`crate::engine::Work`] counts applications, entries touched and operators recomputed, so a
124 /// gate can say "answering this join did not reconsider every pair" without a clock in it —
125 /// which is what `scaling.rs` asserts about a program's loops and now asserts about a query's.
126 pub fn run_measured(
127 &self,
128 schema: &Schema,
129 rows: &dyn read::Rows,
130 ) -> Result<(Vec<Vec<Cell>>, crate::engine::Work), SqlError> {
131 let backend = rows.backend().ok_or_else(|| SqlError {
132 message: "this reader has no executor behind it, so a join, a `group by` and a \
133 `distinct` cannot be answered: they are compiled into the view plan, and a \
134 plan is prepared by a backend"
135 .to_string(),
136 code: "0A000",
137 })?;
138
139 let mut state = Fields::with_capacity(self.inputs.len());
140 for (i, input) in self.inputs.iter().enumerate() {
141 let table = &input.table;
142 let values = scan(schema, table, rows)?;
143 // The query's own name for the table, not the table's: a prefilter says `p.name` when
144 // the `from` said `people p`, and one rule for what a name means is the whole point of
145 // resolving it here the way the joined rows resolve it.
146 let fields: Vec<Field> = table
147 .columns
148 .iter()
149 .map(|c| Field {
150 column: c.clone(),
151 of: Some(input.alias.clone()),
152 })
153 .collect();
154 let ev = Eval::new(schema, &fields, rows);
155 let mut out = Vec::with_capacity(values.len());
156 for v in &values {
157 if !input.prefilter.is_empty() && !ev.holds(&input.prefilter, &table.row(v))? {
158 continue;
159 }
160 out.push(normalise(table, v, input.base));
161 }
162 state.insert(Arc::from(table_field(i)), Value::list(out));
163 }
164 let state = Value::data("Query", None, state);
165
166 let prepared = Prepared::new(Arc::new(self.plan.clone()), backend).map_err(exec)?;
167 let mut engine = Engine::new(Arc::new(prepared));
168 let out = engine
169 .render(&state, &Value::Unit, &Value::Unit)
170 .map_err(exec)?;
171
172 let values: Vec<Value> = match (&out, self.single) {
173 (_, true) => vec![out.clone()],
174 (Value::List(xs), false) => xs.to_vec(),
175 (Value::Map(m), false) => m.iter().map(|(_, v)| v.clone()).collect(),
176 (other, false) => vec![(*other).clone()],
177 };
178 let out = values
179 .iter()
180 .map(|v| {
181 self.fields
182 .iter()
183 .enumerate()
184 .map(|(i, f)| match v.field(&field_name(i)) {
185 Some(x) => read::cell_of(x, &f.column),
186 None => None,
187 })
188 .collect()
189 })
190 .collect();
191 Ok((out, engine.work()))
192 }
193}
194
195fn exec(e: crate::backend::ExecError) -> SqlError {
196 SqlError {
197 message: format!("the query's operators could not be run: {e}"),
198 code: "58000",
199 }
200}
201
202/// A table's rows, from wherever that table's rows come from.
203///
204/// The catalogue and `pg_catalog` are built rather than scanned, and both are tables a join may
205/// name: "which tables have a column called `id`" is a self-join over one, and `psql`'s `\d` is a
206/// join over three of the other.
207fn scan(schema: &Schema, table: &Table, rows: &dyn read::Rows) -> Result<Vec<Value>, SqlError> {
208 match schema.builtin_rows(table) {
209 Some(values) => Ok(values),
210 None => rows.scan(table),
211 }
212}
213
214/// One element as the record the plan reads columns out of: `c{base+i}` per column.
215fn normalise(table: &Table, v: &Value, base: usize) -> Value {
216 Value::data(
217 "Row",
218 None,
219 table
220 .row_values(v)
221 .into_iter()
222 .enumerate()
223 .map(|(i, v)| (Arc::from(field_name(base + i)), v))
224 .collect(),
225 )
226}
227
228/// What a column is called inside the plan, and inside the record it produces.
229fn field_name(i: usize) -> String {
230 format!("c{i}")
231}
232
233/// What a table is called in the record the plan is handed as its state.
234fn table_field(i: usize) -> String {
235 format!("t{i}")
236}
237
238// -------------------------------------------------------------------------------------------
239// Deciding whether the plan is needed at all
240// -------------------------------------------------------------------------------------------
241
242/// Whether a `select` needs the plan.
243///
244/// The boundary is which *operators* the query wants, not how big it is. A scan with a `where` and
245/// an `order by` is what [`crate::read`] has always answered directly and it keeps answering it —
246/// `select count(*)` included, whose whole point is that a maintained arrangement already knows its
247/// own size ([`docs/23`](../../../../../docs/23-incremental-views-report.md) §23.19) and that
248/// compiling a plan to rediscover it would be work for nothing. What comes here is a query that
249/// **relates, groups or deduplicates**, because those are the three things the plan has operators
250/// for and this module has none.
251pub fn relational(s: &Select) -> bool {
252 s.from.len() > 1
253 || !s.group.is_empty()
254 || s.distinct
255 || s.items.iter().any(|i| matches!(i, Item::Aggregate(..)))
256}
257
258// -------------------------------------------------------------------------------------------
259// The compiler
260// -------------------------------------------------------------------------------------------
261
262/// Compile a `select` into a plan over the tables it names.
263pub fn compile(schema: &Schema, s: &Select) -> Result<Compiled, SqlError> {
264 compile_with(schema, s, Relate::default())
265}
266
267/// The same, with [`Relate`] said out loud.
268///
269/// [`docs/08`](../../../../../docs/08-roadmap.md) §8.3 item 8's off switch reaches this surface for
270/// the reason it reaches a program's: the recognition is what turns a nested loop into an indexed
271/// join, and a default nobody has run is a claim. [`Relate::Refuse`] compiles the same expression
272/// to the nested loop it literally is, which is what a gate measures the operator against.
273pub fn compile_with(schema: &Schema, s: &Select, relate: Relate) -> Result<Compiled, SqlError> {
274 let mut q = Query {
275 schema,
276 entries: Vec::new(),
277 fields: Vec::new(),
278 fresh: 0,
279 };
280 q.resolve_from(s)?;
281 if q.entries.is_empty() {
282 return Err(SqlError::syntax(
283 "a join, a `group by` and a `distinct` are all questions about a table, and this \
284 query has no `from`",
285 ));
286 }
287 q.fresh = q.entries.len() as VarId;
288
289 // A comma join's equality lives in the `where`, and it is the same query as a `join … on`:
290 // lifting it gives the indexed operator rather than a cross product the filter then throws
291 // most of away. One that cannot be lifted stays in the `where` and is answered over the pairs,
292 // which is correct and `O(rows × rows)` — the cost of writing it that way.
293 let filter = q.lift_join_conditions(s.filter.clone());
294 let (residual, prefilters) = q.push_down(&filter)?;
295 let grouping = !s.group.is_empty() || s.items.iter().any(Item::aggregates);
296 if !residual.is_empty() && (grouping || s.distinct) {
297 return Err(SqlError::unsupported(format!(
298 "a `where` that spans two tables cannot be applied before a `{}`: every condition here \
299 narrows one table, and an `or` across two of them would have to be a filter over the \
300 joined rows",
301 match grouping {
302 true => "group by",
303 false => "distinct",
304 }
305 )));
306 }
307
308 // The state's fields are positional rather than named after the tables: a query may join a
309 // table to itself, and two fields with one name is not a record.
310 let tables: Vec<Arc<str>> = (0..q.entries.len())
311 .map(|i| Arc::from(table_field(i)))
312 .collect();
313 let rows = q.rows_expression();
314
315 // The stages below read the joined rows more than once — a `group by` reads them once for its
316 // keys and once per aggregate — so the rows are **bound** rather than rebuilt. A `let` is what
317 // makes that one node with several consumers rather than several nodes computing the same
318 // thing, which is §5.3's sharing at the granularity the plan shares at.
319 let bound = q.fresh();
320 let rows_var = var(bound);
321
322 let (body, fields, projected, single) = if grouping {
323 let (body, fields) = q.grouped(s, bound)?;
324 (body, fields, true, s.group.is_empty())
325 } else if s.distinct {
326 let param = q.fresh();
327 let (row, fields) = q.project(s, param)?;
328 let body = prim(
329 Prim::ListUnique,
330 vec![prim(
331 Prim::MapList,
332 vec![rows_var.clone(), lam(vec![param], make(row))],
333 )],
334 );
335 (body, fields, true, false)
336 } else {
337 (rows_var.clone(), q.fields.clone(), false, false)
338 };
339 // `distinct` over a grouped query is `list_unique` over the groups, which is what the two words
340 // mean together; the operator is the same one either way.
341 let body = match s.distinct && grouping && !single {
342 true => prim(Prim::ListUnique, vec![body]),
343 false => body,
344 };
345
346 let body = bind(bound, rows, body);
347 let plan = Plan::of_query_with(&tables, &body, relate);
348 let inputs = q
349 .entries
350 .iter()
351 .zip(prefilters)
352 .map(|(e, prefilter)| Input {
353 table: e.table.clone(),
354 alias: e.alias.clone(),
355 base: e.base,
356 prefilter,
357 })
358 .collect();
359 Ok(Compiled {
360 fields,
361 projected,
362 residual,
363 inputs,
364 plan,
365 single,
366 })
367}
368
369/// One `from` entry, resolved.
370struct Entry<'a> {
371 alias: Arc<str>,
372 table: &'a Table,
373 /// Where this table's columns start in the wide row.
374 base: usize,
375 /// The `on` equalities as (this table's column, an earlier table's column).
376 on: Vec<(usize, usize)>,
377 /// `left join`: a row before this one survives with nulls when this table has no match.
378 left: bool,
379}
380
381struct Query<'a> {
382 schema: &'a Schema,
383 entries: Vec<Entry<'a>>,
384 /// Every column of every table, in `from` order — the row a join produces.
385 fields: Vec<Field>,
386 fresh: VarId,
387}
388
389impl<'a> Query<'a> {
390 fn fresh(&mut self) -> VarId {
391 self.fresh += 1;
392 self.fresh - 1
393 }
394
395 /// Resolve the `from` list: the tables, their names in this query, and where each one's columns
396 /// sit in the row a join produces.
397 fn resolve_from(&mut self, s: &'a Select) -> Result<(), SqlError> {
398 for (i, f) in s.from.iter().enumerate() {
399 if let Some(call) = &f.function {
400 return Err(SqlError::unsupported(format!(
401 "`{call}(…)` in a `from` is a set-returning function, and this read model has \
402 none: what a `from` names here is a relation"
403 )));
404 }
405 let table = self.schema.relation(f.namespace.as_deref(), &f.table)?;
406 if self.entries.iter().any(|e| e.alias.as_ref() == f.alias) {
407 return Err(SqlError::syntax(format!(
408 "\"{}\" is in this `from` twice; give one of them a name (`{} as x`)",
409 f.alias, f.table
410 )));
411 }
412 let base = self.fields.len();
413 self.fields.extend(table.columns.iter().map(|c| Field {
414 column: c.clone(),
415 of: Some(Arc::from(f.alias.as_str())),
416 }));
417 self.entries.push(Entry {
418 alias: Arc::from(f.alias.as_str()),
419 table,
420 base,
421 on: Vec::new(),
422 left: f.left,
423 });
424 // Resolved after the entry exists, so a join may name its own columns.
425 let mut on = Vec::new();
426 for (l, r) in &f.on {
427 let (li, ri) = (self.resolve(l)?, self.resolve(r)?);
428 let pair = match (li >= base, ri >= base) {
429 (true, false) => (li, ri),
430 (false, true) => (ri, li),
431 _ => {
432 return Err(SqlError::unsupported(format!(
433 "`on {l} = {r}` does not join \"{}\" to a table before it: one side \
434 has to be a column of \"{}\" and the other a column of a table \
435 already in the `from`",
436 f.alias, f.alias
437 )))
438 }
439 };
440 // Two conditions on a join key, and both are the SQL semantics this operator does
441 // not have rather than an implementation gap. `==` here is [`Value`]'s own equality
442 // — the order the index is a `BTreeMap` in — so a `NULL` would equal a `NULL`,
443 // where SQL says it equals nothing; and `Int` and `Float` are different values,
444 // where SQL would coerce. Both are refused rather than answered differently from
445 // every other database.
446 let (a, b) = (&self.fields[pair.0], &self.fields[pair.1]);
447 if a.column.nullable || b.column.nullable {
448 return Err(SqlError::unsupported(format!(
449 "`on {l} = {r}` joins on a column that can be null, and this join's \
450 equality is the index's own: a null would match a null, where SQL says a \
451 null matches nothing"
452 )));
453 }
454 if a.column.ty != b.column.ty {
455 return Err(SqlError::unsupported(format!(
456 "`on {l} = {r}` compares {} with {}, and this join's equality does not \
457 coerce between them: give the two columns the same type",
458 a.column.ty.name(),
459 b.column.ty.name()
460 )));
461 }
462 on.push(pair);
463 }
464 if i > 0 && on.is_empty() && !f.on.is_empty() {
465 return Err(SqlError::syntax(format!(
466 "`join {}` wants `on <column> = <column>`",
467 f.table
468 )));
469 }
470 self.entries[i].on = on;
471 }
472 Ok(())
473 }
474
475 /// A column reference, as an index into the wide row.
476 fn resolve(&self, n: &Name) -> Result<usize, SqlError> {
477 let matching: Vec<usize> = (0..self.fields.len())
478 .filter(|&i| {
479 let f = &self.fields[i];
480 f.column.name.as_ref() == n.column
481 && match &n.table {
482 Some(t) => f.of.as_deref() == Some(t.as_str()),
483 None => true,
484 }
485 })
486 .collect();
487 match matching.as_slice() {
488 [one] => Ok(*one),
489 [] => Err(SqlError::no_column(match &n.table {
490 Some(t) if !self.entries.iter().any(|e| e.alias.as_ref() == t.as_str()) => {
491 format!(
492 "\"{t}\" is not a table in this query; it has {}",
493 self.entries
494 .iter()
495 .map(|e| format!("\"{}\"", e.alias))
496 .collect::<Vec<_>>()
497 .join(", ")
498 )
499 }
500 _ => format!(
501 "there is no column \"{n}\" here; there is {}",
502 read::names_of(&self.fields)
503 ),
504 })),
505 _ => Err(SqlError::no_column(format!(
506 "\"{n}\" is ambiguous: more than one table in this query has a column called \
507 \"{}\", so qualify it — `t.{}`",
508 n.column, n.column
509 ))),
510 }
511 }
512
513 /// Split the `where` into what each table can be scanned with, and what is left over.
514 ///
515 /// A term that names one table narrows that table's scan; one that names none is a constant
516 /// and narrows the first (so a `where false` reads nothing rather than everything); one that
517 /// spans two is left for the joined rows.
518 ///
519 /// A prefilter is what makes a `where` cost `O(rows)` on the table it names rather than
520 /// `O(rows × rows)` on the join — the rows it removes are rows no stage ever pairs.
521 ///
522 /// **A term over a `left join`'s own table is never pushed into it.** Removing a row from the
523 /// null-supplying side does not remove the joined row: it turns it into one with nulls, which
524 /// the term would then have rejected. `psql`'s `\d` is exactly this — `left join pg_namespace
525 /// n … where n.nspname <> 'pg_catalog'` — and pushing it down answers with every relation the
526 /// query asked to hide.
527 fn push_down(&self, filter: &Filter) -> Result<(Filter, Vec<Filter>), SqlError> {
528 let mut prefilters: Vec<Filter> = vec![Vec::new(); self.entries.len()];
529 let mut residual = Vec::new();
530 for term in filter {
531 let mut names = Vec::new();
532 term.names(&mut names);
533 // A name this query cannot resolve — one belonging to a subquery the term carries, or
534 // one that is simply wrong — makes the term unpushable rather than an error here: it
535 // is applied to the joined rows, where resolving it is what a person is shown.
536 let mut owners = BTreeSet::new();
537 let mut resolved = true;
538 for name in &names {
539 match self.resolve(name) {
540 Ok(i) => {
541 owners.insert(self.owner(i));
542 }
543 Err(_) => resolved = false,
544 }
545 }
546 if !resolved {
547 residual.push(term.clone());
548 continue;
549 }
550 match owners.iter().copied().collect::<Vec<_>>().as_slice() {
551 [] => prefilters[0].push(term.clone()),
552 [one] if !self.entries[*one].left => prefilters[*one].push(term.clone()),
553 _ => residual.push(term.clone()),
554 }
555 }
556 Ok((residual, prefilters))
557 }
558
559 /// Move `where a.k = b.k` into the `on` of whichever entry it joins, and answer what is left.
560 ///
561 /// It applies only to an entry that has no `on` of its own and is not a `left join`: those are
562 /// the entries a comma join and a `cross join` produce, and they are the ones whose equality
563 /// was written in the `where` because the syntax has nowhere else to put it. Everything the
564 /// lift does not recognise stays where it was, so this can make a query faster and cannot make
565 /// one answer differently.
566 fn lift_join_conditions(&mut self, terms: Filter) -> Filter {
567 let mut kept = Vec::with_capacity(terms.len());
568 for term in terms {
569 let read::Expr::Cmp(l, read::CmpOp::Eq, r) = &term else {
570 kept.push(term);
571 continue;
572 };
573 let (read::Expr::Column(l), read::Expr::Column(r)) = (&**l, &**r) else {
574 kept.push(term);
575 continue;
576 };
577 let (Ok(li), Ok(ri)) = (self.resolve(l), self.resolve(r)) else {
578 kept.push(term);
579 continue;
580 };
581 let (lo, hi) = (self.owner(li), self.owner(ri));
582 // The later entry is the one being joined; the earlier one is what it joins to.
583 let (entry, pair) = match lo.cmp(&hi) {
584 std::cmp::Ordering::Less => (hi, (ri, li)),
585 std::cmp::Ordering::Greater => (lo, (li, ri)),
586 std::cmp::Ordering::Equal => {
587 kept.push(term);
588 continue;
589 }
590 };
591 let e = &self.entries[entry];
592 let usable = entry > 0
593 && e.on.is_empty()
594 && !e.left
595 && !self.fields[pair.0].column.nullable
596 && !self.fields[pair.1].column.nullable
597 && self.fields[pair.0].column.ty == self.fields[pair.1].column.ty;
598 match usable {
599 true => self.entries[entry].on.push(pair),
600 false => kept.push(term),
601 }
602 }
603 kept
604 }
605
606 /// Which `from` entry a wide column belongs to.
607 fn owner(&self, column: usize) -> usize {
608 self.entries
609 .iter()
610 .rposition(|e| e.base <= column)
611 .unwrap_or(0)
612 }
613
614 /// The expression whose value is the rows the `from` list produces.
615 ///
616 /// One stage per join, left-deep — `concat_lists(map_list(prev, λr. map_list(filter_list(t, λy.
617 /// y.k == r.k), λy. row)))`. Each stage is a `map_list` of its own in the plan and therefore
618 /// gets its own [`crate::plan::Op::Join`] over its own index; nesting them inside one
619 /// per-element function would index the first join and leave the rest as nested loops, which is
620 /// the cost this exists to remove.
621 fn rows_expression(&mut self) -> Core {
622 let mut rows = var(0);
623 for i in 1..self.entries.len() {
624 let left = self.fresh();
625 let right = self.fresh();
626 let base = self.entries[i].base;
627 let width = self.entries[i].table.columns.len();
628 let pairs = self.entries[i].on.clone();
629 let (no_key, outer) = (pairs.is_empty(), self.entries[i].left);
630 let key_of = |v: VarId, mine: bool| -> Core {
631 let at = |p: &(usize, usize)| if mine { p.0 } else { p.1 };
632 match pairs.as_slice() {
633 [one] => field(var(v), &field_name(at(one))),
634 many => make(
635 many.iter()
636 .enumerate()
637 .map(|(k, p)| {
638 (
639 Arc::from(format!("k{k}")),
640 field(var(v), &field_name(at(p))),
641 )
642 })
643 .collect(),
644 ),
645 }
646 };
647 let combined = make(
648 (0..base)
649 .map(|k| (Arc::from(field_name(k)), field(var(left), &field_name(k))))
650 .chain(
651 (base..base + width)
652 .map(|k| (Arc::from(field_name(k)), field(var(right), &field_name(k)))),
653 )
654 .collect(),
655 );
656 // A `from` entry with no `on` is a cross product — a comma join whose equality stayed
657 // in the `where`, or a `cross join`. It is the loop without the filter, and the `where`
658 // then narrows the pairs it produced.
659 let source = match no_key {
660 true => var(i as VarId),
661 false => prim(
662 Prim::FilterList,
663 vec![
664 var(i as VarId),
665 lam(
666 vec![right],
667 prim(Prim::Eq, vec![key_of(right, true), key_of(left, false)]),
668 ),
669 ],
670 ),
671 };
672 let matched = prim(Prim::MapList, vec![source, lam(vec![right], combined)]);
673 // A `left join` keeps the left row when the group is empty, with this table's columns
674 // as units — which `read::cell_of` reads as the NULL SQL says they are. The group is
675 // **bound** rather than built twice: it is what the emptiness is a question about and
676 // what the answer is when it is not empty.
677 let inner = match outer {
678 false => matched,
679 true => {
680 let group = self.fresh();
681 let empty = make(
682 (0..base)
683 .map(|k| (Arc::from(field_name(k)), field(var(left), &field_name(k))))
684 .chain((base..base + width).map(|k| {
685 (Arc::from(field_name(k)), node(CoreKind::Const(Const::Unit)))
686 }))
687 .collect(),
688 );
689 bind(
690 group,
691 matched,
692 node(CoreKind::If {
693 cond: Box::new(prim(Prim::ListIsEmpty, vec![var(group)])),
694 then: Box::new(node(CoreKind::ListLit(vec![empty]))),
695 alt: Box::new(var(group)),
696 }),
697 )
698 }
699 };
700 rows = prim(
701 Prim::ConcatLists,
702 vec![prim(Prim::MapList, vec![rows, lam(vec![left], inner)])],
703 );
704 }
705 rows
706 }
707
708 /// The select list as the fields of one record per row, and what its columns are called.
709 fn project(&mut self, s: &Select, param: VarId) -> Result<(Row, Vec<Field>), SqlError> {
710 let mut out = Vec::new();
711 let mut fields = Vec::new();
712 for item in &s.items {
713 match item {
714 Item::All(qualifier) => {
715 let mut any = false;
716 for i in 0..self.fields.len() {
717 if let Some(t) = qualifier {
718 if self.fields[i].of.as_deref() != Some(t.as_str()) {
719 continue;
720 }
721 }
722 any = true;
723 let f = self.fields[i].clone();
724 push(&mut out, &mut fields, f, field(var(param), &field_name(i)));
725 }
726 if !any {
727 return Err(SqlError::no_table(format!(
728 "\"{}\" is not a table in this query",
729 qualifier.as_deref().unwrap_or("")
730 )));
731 }
732 }
733 Item::Column(name, alias) => {
734 let i = self.resolve(name)?;
735 let mut f = self.fields[i].clone();
736 if let Some(a) = alias {
737 f.column.name = Arc::from(a.as_str());
738 f.of = None;
739 }
740 push(&mut out, &mut fields, f, field(var(param), &field_name(i)));
741 }
742 Item::Literal(d, alias) => {
743 let f = literal_field(d, alias.as_deref());
744 push(&mut out, &mut fields, f, constant(d));
745 }
746 Item::Count(_) | Item::Aggregate(..) => {
747 return Err(SqlError::unsupported(
748 "`select distinct` over an aggregate has nothing to be distinct about; a \
749 `group by` is what asks an aggregate per group",
750 ))
751 }
752 // An expression in a `select distinct` or a `group by` would have to be compiled
753 // into the operator rather than evaluated over what it produced: the operator's
754 // key *is* the select list, so a `case` there is a `case` inside a
755 // `list_unique`. Nothing asks for one, and answering it wrongly would be worse
756 // than saying so.
757 Item::Expr(_, alias) => {
758 return Err(SqlError::unsupported(format!(
759 "`{}` is an expression, and a `{}` is over the columns it groups by: an \
760 expression here would have to be part of the key the operator builds",
761 alias.as_deref().unwrap_or("an expression"),
762 match s.group.is_empty() {
763 true => "select distinct",
764 false => "group by",
765 }
766 )))
767 }
768 }
769 }
770 Ok((out, fields))
771 }
772
773 /// A `group by`, or an aggregate with no `group by` — which is the same question asked of the
774 /// whole collection rather than of a group.
775 ///
776 /// The grouped form is the loop `corpus/35-workload.beck` writes by hand: the distinct keys are
777 /// the rows, and each aggregate is a question about the filter that would have built the group.
778 /// [`crate::relate`] reads exactly that shape, so what the plan ends up with is a
779 /// [`crate::plan::Op::ArrangeBy`] for the counts, a [`crate::plan::Op::GroupBy`] per other
780 /// aggregate, and a join per question — and no group is ever built.
781 fn grouped(&mut self, s: &Select, rows: VarId) -> Result<(Core, Vec<Field>), SqlError> {
782 let keys: Vec<usize> = s
783 .group
784 .iter()
785 .map(|n| self.resolve(n))
786 .collect::<Result<_, _>>()?;
787 let ungrouped = keys.is_empty();
788 let element = self.fresh();
789
790 let mut out = Vec::new();
791 let mut fields = Vec::new();
792 for item in &s.items {
793 match item {
794 Item::All(_) => {
795 return Err(SqlError::unsupported(
796 "`select *` with a `group by` would name every column of every row, and a \
797 group is not a row: name the grouped columns and the aggregates",
798 ))
799 }
800 // See `project`: an expression's place in a grouped query is inside the key the
801 // operator builds, and this evaluates expressions over the rows one produced.
802 Item::Expr(_, alias) => {
803 return Err(SqlError::unsupported(format!(
804 "`{}` is an expression, and a `group by` groups by columns: an expression \
805 here would have to be part of the key the operator builds",
806 alias.as_deref().unwrap_or("an expression")
807 )))
808 }
809 Item::Column(name, alias) => {
810 let i = self.resolve(name)?;
811 let at = keys.iter().position(|&k| k == i).ok_or_else(|| SqlError {
812 message: format!(
813 "\"{name}\" is not in the `group by`, so a group has more than one of \
814 it: put it in the `group by`, or ask an aggregate for it"
815 ),
816 code: "42803",
817 })?;
818 let mut f = self.fields[i].clone();
819 if let Some(a) = alias {
820 f.column.name = Arc::from(a.as_str());
821 f.of = None;
822 }
823 let value = match keys.len() {
824 1 => var(element),
825 _ => field(var(element), &format!("k{at}")),
826 };
827 push(&mut out, &mut fields, f, value);
828 }
829 Item::Literal(d, alias) => {
830 let f = literal_field(d, alias.as_deref());
831 push(&mut out, &mut fields, f, constant(d));
832 }
833 Item::Count(alias) => {
834 let f = Field {
835 column: Column {
836 name: Arc::from(alias.as_deref().unwrap_or("count")),
837 ty: SqlTy::Bigint,
838 nullable: false,
839 },
840 of: None,
841 };
842 let over = match ungrouped {
843 true => var(rows),
844 false => self.group_filter(rows, element, &keys),
845 };
846 push(&mut out, &mut fields, f, prim(Prim::ListLen, vec![over]));
847 }
848 Item::Aggregate(agg, name, alias) => {
849 let i = self.resolve(name)?;
850 let source = self.fields[i].clone();
851 if source.column.nullable {
852 return Err(SqlError::unsupported(format!(
853 "`{}({name})` is not answered here because \"{name}\" is an `Option` \
854 and SQL's aggregates skip nulls: this one is a function of what every \
855 row contributes, so a row contributing nothing has no answer",
856 agg.name()
857 )));
858 }
859 if *agg == Agg::Sum && source.column.ty != SqlTy::Bigint {
860 return Err(SqlError::unsupported(format!(
861 "`sum({name})` is not answered here because \"{name}\" is {} and a \
862 total over it would depend on the order it was added in — Beck's \
863 `list_sum` is exact over `Int` and has no `Float` form at all \
864 (docs/46 §46.16)",
865 source.column.ty.name()
866 )));
867 }
868 let f = Field {
869 column: Column {
870 name: Arc::from(
871 alias.clone().unwrap_or_else(|| agg.name().to_string()),
872 ),
873 ty: source.column.ty,
874 nullable: *agg != Agg::Sum,
875 },
876 of: None,
877 };
878 let over = match ungrouped {
879 true => var(rows),
880 false => self.group_filter(rows, element, &keys),
881 };
882 let row = self.fresh();
883 let projected = prim(
884 Prim::MapList,
885 vec![over, lam(vec![row], field(var(row), &field_name(i)))],
886 );
887 let op = match agg {
888 Agg::Min => Prim::ListMin,
889 Agg::Max => Prim::ListMax,
890 Agg::Sum => Prim::ListSum,
891 };
892 push(&mut out, &mut fields, f, prim(op, vec![projected]));
893 }
894 }
895 }
896
897 if ungrouped {
898 return Ok((make(out), fields));
899 }
900 let key_param = self.fresh();
901 let distinct = prim(
902 Prim::ListUnique,
903 vec![prim(
904 Prim::MapList,
905 vec![var(rows), lam(vec![key_param], group_key(&keys, key_param))],
906 )],
907 );
908 Ok((
909 prim(Prim::MapList, vec![distinct, lam(vec![element], make(out))]),
910 fields,
911 ))
912 }
913
914 /// `filter_list(R, λr. g(r) == k)` — the group, as the expression an aggregate asks about.
915 ///
916 /// Never evaluated as written: [`crate::relate`] reads it as the probe of an index keyed by
917 /// `g`, and what the operator above it asks for decides whether a group is built at all. A
918 /// count is answered from the join's tally and an extreme from
919 /// [`crate::plan::Op::GroupBy`]'s multiset, so this expression is the *question* rather than
920 /// the work.
921 fn group_filter(&mut self, rows: VarId, element: VarId, keys: &[usize]) -> Core {
922 let row = self.fresh();
923 let predicate = prim(Prim::Eq, vec![group_key(keys, row), var(element)]);
924 prim(Prim::FilterList, vec![var(rows), lam(vec![row], predicate)])
925 }
926}
927
928/// The value a group's key has, as a function of one row: the column itself when a query groups by
929/// one, and a record of them when it groups by several — which is a key a `BTreeMap` orders exactly
930/// as `==` compares it, so the index and the equality agree by construction.
931fn group_key(keys: &[usize], v: VarId) -> Core {
932 match keys {
933 [one] => field(var(v), &field_name(*one)),
934 many => make(
935 many.iter()
936 .enumerate()
937 .map(|(j, &i)| (Arc::from(format!("k{j}")), field(var(v), &field_name(i))))
938 .collect(),
939 ),
940 }
941}
942
943fn push(out: &mut Row, fields: &mut Vec<Field>, f: Field, value: Core) {
944 out.push((Arc::from(field_name(fields.len())), value));
945 fields.push(f);
946}
947
948fn literal_field(d: &Datum, alias: Option<&str>) -> Field {
949 Field {
950 column: Column {
951 name: Arc::from(alias.unwrap_or("?column?")),
952 ty: d.ty(),
953 nullable: false,
954 },
955 of: None,
956 }
957}
958
959// -------------------------------------------------------------------------------------------
960// Small `Core` constructors
961// -------------------------------------------------------------------------------------------
962//
963// Types are `unit` throughout and that is not laziness: a plan runs after the checker, nothing
964// downstream reads a type off these nodes, and giving them invented ones would be a second, wrong
965// answer to a question this expression was never asked.
966
967fn node(kind: CoreKind) -> Core {
968 Core {
969 kind,
970 ty: Ty::unit(),
971 tier: Tier::Any,
972 span: Span::NONE,
973 last_use: false,
974 order: crate::fields::UNORDERED,
975 locals: 0,
976 }
977}
978
979fn var(v: VarId) -> Core {
980 node(CoreKind::Var(v))
981}
982
983fn field(base: Core, name: &str) -> Core {
984 node(CoreKind::Field {
985 base: Box::new(base),
986 name: Arc::from(name),
987 })
988}
989
990fn prim(op: Prim, args: Vec<Core>) -> Core {
991 node(CoreKind::Prim { op, args })
992}
993
994fn make(fields: Row) -> Core {
995 node(CoreKind::Make {
996 ty: Arc::from("Row"),
997 variant: None,
998 fields,
999 })
1000}
1001
1002fn lam(params: Vec<VarId>, body: Core) -> Core {
1003 node(CoreKind::Lam {
1004 params: params.into(),
1005 body: Arc::new(body),
1006 })
1007}
1008
1009fn bind(v: VarId, value: Core, body: Core) -> Core {
1010 node(CoreKind::Let {
1011 var: v,
1012 value: Box::new(value),
1013 body: Box::new(body),
1014 })
1015}
1016
1017fn constant(d: &Datum) -> Core {
1018 node(CoreKind::Const(match d {
1019 Datum::Boolean(b) => Const::Bool(*b),
1020 Datum::Bigint(i) => Const::Int(*i),
1021 Datum::Double(f) => Const::Float(*f),
1022 Datum::Text(s) => Const::Str(Arc::from(s.as_str())),
1023 }))
1024}