beck_core/
read.rs

1//! The read model: a program's maintained state as relations, and a small SQL over them.
2//!
3//! [`docs/05-tier-lowering.md`](../../../../../docs/05-tier-lowering.md) §5.3 names this as one of
4//! the four things the data tier owes:
5//!
6//! > Read models … one-shot queries and **pgwire access for the outside world**: `psql`, BI tools,
7//! > DBeaver see materialized views as ordinary tables — the single cheapest trust-builder for
8//! > adopting teams
9//!
10//! # What a read model is here, and what it is not
11//!
12//! §5.3's row also says "generated tables in the same Postgres", and that is **not** what this
13//! builds. A read model is not a second copy of the state written on the append path; it is the
14//! collection the fold already holds and the arrangement [`crate::engine`] already maintains,
15//! *projected*. Three consequences, and they are the argument for it:
16//!
17//! * **A read model costs nothing per event.** Nothing is written, nothing is projected, and the
18//!   sequencer is untouched — which is [`26`](../../../../../docs/26-arrangement-sharing-report.md)
19//!   §26.2's rule ("who advances it: not the sequencer") applied to a second kind of reader rather
20//!   than argued with.
21//! * **It cannot disagree with the page.** A durable projection is a second code path, and a second
22//!   code path over the same events is a thing that can drift. These rows are read from the same
23//!   arrangement the view renders from, so the recompute oracle already covers them.
24//! * **It is exactly as fresh as the query.** A query advances the dataflow to the log's head and
25//!   then reads, so a `SELECT` issued after an ack sees that ack's event. There is no projection
26//!   lag because there is no projection.
27//!
28//! What that costs is the one-transaction property [`67`](../../../../../docs/67-sqlite-report.md)
29//! §67.1 held open: an append and its projection are still not one transaction, because there is
30//! still no projection. §88.6 is the row-by-row list.
31//!
32//! # Where the tables come from
33//!
34//! | Table | Rows | Read from |
35//! |---|---|---|
36//! | a collection-valued field of the accumulator | its elements | the state value |
37//! | the accumulator's remaining scalar fields | one | the state value |
38//! | a declared signal that does not read the session | its elements, or one | the maintained node |
39//!
40//! The third row is the interesting one: **a read model is a view that does not depend on who is
41//! asking**, which is the same cut §5.3 draws for arrangement sharing. A `per_session` signal is not
42//! a table because a SQL client is not a session — it has no `Session` to be rendered for, and
43//! inventing one would answer a question nobody asked.
44//!
45//! # What this is not
46//!
47//! It is not a query planner. [`04`](../../../../../docs/04-compiler-architecture.md) §4.2 keeps the
48//! `Query` sub-language symbolic, and §20.5 holds `beck explain query` until an engine compiles one;
49//! what [`parse`] accepts is a hand-written subset over one table at a time, with no joins, no
50//! subqueries and no aggregation beyond `count(*)`. It exists so that an outside tool can read what
51//! the program holds, which is what §5.3's row is for.
52
53use std::collections::{BTreeMap, BTreeSet};
54use std::fmt::Write as _;
55use std::sync::Arc;
56
57use crate::core::Value;
58use crate::plan::{OpId, Plan};
59use crate::split::Placed;
60use crate::ty::{Ty, TyDecl};
61
62// -------------------------------------------------------------------------------------------
63// Types
64// -------------------------------------------------------------------------------------------
65
66/// The four SQL types a Beck scalar maps onto.
67///
68/// Deliberately four. Every one of them is a type OID a Postgres client already knows, so a driver
69/// never has to ask the catalogue what it just received — which matters more than breadth here,
70/// because there is no catalogue to ask ([`Schema::CATALOGUE`] is what stands in for one).
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72pub enum SqlTy {
73    Boolean,
74    Bigint,
75    Double,
76    Text,
77}
78
79impl SqlTy {
80    /// The Postgres type OID, as it goes on the wire in a `RowDescription`.
81    pub fn oid(self) -> u32 {
82        match self {
83            SqlTy::Boolean => 16,
84            SqlTy::Bigint => 20,
85            SqlTy::Text => 25,
86            SqlTy::Double => 701,
87        }
88    }
89
90    /// The width a fixed-size type has, or -1 for a variable one.
91    pub fn width(self) -> i16 {
92        match self {
93            SqlTy::Boolean => 1,
94            SqlTy::Bigint | SqlTy::Double => 8,
95            SqlTy::Text => -1,
96        }
97    }
98
99    pub fn name(self) -> &'static str {
100        match self {
101            SqlTy::Boolean => "boolean",
102            SqlTy::Bigint => "bigint",
103            SqlTy::Double => "double precision",
104            SqlTy::Text => "text",
105        }
106    }
107}
108
109#[derive(Clone, Debug)]
110pub struct Column {
111    pub name: Arc<str>,
112    pub ty: SqlTy,
113    /// Only an `Option[T]` field is nullable. Beck has no null, so this is the one place one comes
114    /// from — and a column that is not an `Option` never holds one.
115    pub nullable: bool,
116}
117
118/// Where a table's rows are read from.
119#[derive(Clone, Debug, PartialEq, Eq)]
120pub enum Source {
121    /// A path of field names from the accumulator to a collection, or to the accumulator itself.
122    ///
123    /// Read from the state value the fold produced, not from an arrangement: a base table's rows
124    /// *are* the fold's collection, and a scan is `O(rows)` in any database.
125    State(Vec<Arc<str>>),
126    /// A plan operator that does not read the session, read from the maintained dataflow.
127    ///
128    /// This is the one that earns the engine its keep: the rows of a derived table are whatever the
129    /// arrangement holds, and the arrangement was maintained for the page.
130    View(OpId),
131    /// The schema describing itself.
132    Catalogue,
133}
134
135/// How many rows a table can have, which is a fact about its shape rather than about its data.
136#[derive(Clone, Copy, Debug, PartialEq, Eq)]
137pub enum Cardinality {
138    /// A collection: as many rows as it has elements.
139    Many,
140    /// A record or a scalar: exactly one row, always.
141    One,
142}
143
144#[derive(Clone, Debug)]
145pub struct Table {
146    pub name: Arc<str>,
147    pub columns: Vec<Column>,
148    pub source: Source,
149    pub cardinality: Cardinality,
150    /// The Beck type one row stands for, for `beck explain sql` to print.
151    pub element: Arc<str>,
152}
153
154impl Table {
155    pub fn column(&self, name: &str) -> Option<(usize, &Column)> {
156        self.columns
157            .iter()
158            .enumerate()
159            .find(|(_, c)| c.name.as_ref() == name)
160    }
161
162    /// One value as one row, coerced to the columns this table declares.
163    ///
164    /// Coerced rather than trusted: the column types come from the *declared* type and the value
165    /// comes from a running program, so a value that does not fit its column becomes NULL rather
166    /// than a wrongly-encoded field on the wire. Nothing in the corpus reaches that branch; the
167    /// branch is there because "cannot happen" is not a wire format.
168    pub fn row(&self, v: &Value) -> Vec<Cell> {
169        match (&self.cardinality, unwrap(v)) {
170            // A record: one column per field, by name.
171            (_, Value::Data(d)) if d.variant.is_none() && !d.fields.is_empty() => self
172                .columns
173                .iter()
174                .map(|c| match d.fields.get(&c.name) {
175                    Some(f) => cell(f, c),
176                    None => None,
177                })
178                .collect(),
179            // A scalar, or anything else: the single column this table then has.
180            (_, other) => self
181                .columns
182                .iter()
183                .map(|c| cell(other, c))
184                .collect::<Vec<_>>(),
185        }
186    }
187}
188
189/// A value in one column, or SQL NULL.
190pub type Cell = Option<Datum>;
191
192#[derive(Clone, Debug, PartialEq)]
193pub enum Datum {
194    Boolean(bool),
195    Bigint(i64),
196    Double(f64),
197    Text(String),
198}
199
200impl Datum {
201    pub fn ty(&self) -> SqlTy {
202        match self {
203            Datum::Boolean(_) => SqlTy::Boolean,
204            Datum::Bigint(_) => SqlTy::Bigint,
205            Datum::Double(_) => SqlTy::Double,
206            Datum::Text(_) => SqlTy::Text,
207        }
208    }
209
210    /// The text form, which is both what the simple query protocol sends and what `ORDER BY`
211    /// compares for a text column.
212    pub fn text(&self) -> String {
213        match self {
214            Datum::Boolean(b) => if *b { "t" } else { "f" }.to_string(),
215            Datum::Bigint(i) => i.to_string(),
216            // Postgres prints a float with enough digits to round-trip, and so does Rust's `{}`
217            // for `f64` — except that Rust drops the fractional part of a whole number, where
218            // Postgres keeps none either. `1` and `1` agree; nothing here needs `1.0`.
219            Datum::Double(f) => f.to_string(),
220            Datum::Text(s) => s.clone(),
221        }
222    }
223}
224
225/// One Beck value in one column, or NULL.
226fn cell(v: &Value, c: &Column) -> Cell {
227    let v = unwrap(v);
228    // `None` is the only null this language has, and it is only reachable through an `Option`
229    // column — a non-nullable column holding one would be a value that does not fit its type.
230    if let Value::Data(d) = v {
231        if d.variant.as_deref() == Some("None") {
232            return None;
233        }
234        if d.variant.as_deref() == Some("Some") {
235            return match d.fields.values().next() {
236                Some(inner) => cell(inner, c),
237                None => None,
238            };
239        }
240    }
241    match (c.ty, v) {
242        (SqlTy::Boolean, Value::Bool(b)) => Some(Datum::Boolean(*b)),
243        (SqlTy::Bigint, Value::Int(i)) => Some(Datum::Bigint(*i)),
244        (SqlTy::Double, _) => v.as_f64().map(Datum::Double),
245        (SqlTy::Text, Value::Str(s)) => Some(Datum::Text(s.to_string())),
246        // A composite column — a list, a map, a nested record, a union variant. JSON is the wire
247        // form this language already has for a value a browser reads (`Value::to_json`), so it is
248        // the one a SQL client gets too rather than a second rendering invented here.
249        (SqlTy::Text, other) => Some(Datum::Text(match other {
250            Value::Unit => return None,
251            _ => serde_json::to_string(&other.to_json()).unwrap_or_else(|_| other.display()),
252        })),
253        _ => None,
254    }
255}
256
257/// See through a newtype, which at run time is a one-field record with no variant.
258fn unwrap(v: &Value) -> &Value {
259    match v {
260        Value::Data(d) if d.variant.is_none() && d.fields.len() == 1 => {
261            match d.fields.values().next() {
262                Some(inner) => unwrap(inner),
263                None => v,
264            }
265        }
266        _ => v,
267    }
268}
269
270// -------------------------------------------------------------------------------------------
271// The schema
272// -------------------------------------------------------------------------------------------
273
274/// Every table a program's read model has.
275#[derive(Clone, Debug, Default)]
276pub struct Schema {
277    pub tables: Vec<Table>,
278}
279
280impl Schema {
281    /// The name of the catalogue table. There is no `pg_catalog` here, and a client that cannot
282    /// find out what exists cannot use what exists, so the schema is a table in itself.
283    pub const CATALOGUE: &'static str = "beck_columns";
284
285    pub fn table(&self, name: &str) -> Option<&Table> {
286        self.tables.iter().find(|t| t.name.as_ref() == name)
287    }
288
289    /// Derive the read model of a sliced program.
290    pub fn of(placed: &Placed, plan: &Plan) -> Schema {
291        let types = &placed.program.types;
292        let mut tables: Vec<Table> = Vec::new();
293        let mut taken: BTreeSet<Arc<str>> = BTreeSet::new();
294
295        for role in &placed.roles.states {
296            let base: Vec<Arc<str>> = role.field.iter().cloned().collect();
297            let ty = resolve(&role.ty, types);
298            match collection_elem(&ty, types) {
299                // The whole accumulator is a collection: one table, named after the fold.
300                Some(elem) => push(
301                    &mut tables,
302                    &mut taken,
303                    table(
304                        role.name.clone(),
305                        &elem,
306                        types,
307                        Source::State(base),
308                        Cardinality::Many,
309                    ),
310                ),
311                None => {
312                    let fields = model_fields(&ty, types).unwrap_or_default();
313                    let mut scalars: Vec<(Arc<str>, Ty)> = Vec::new();
314                    for (name, fty) in fields {
315                        let fty = resolve(&fty, types);
316                        match collection_elem(&fty, types) {
317                            Some(elem) => {
318                                let mut path = base.clone();
319                                path.push(name.clone());
320                                push(
321                                    &mut tables,
322                                    &mut taken,
323                                    table(
324                                        name,
325                                        &elem,
326                                        types,
327                                        Source::State(path),
328                                        Cardinality::Many,
329                                    ),
330                                );
331                            }
332                            None => scalars.push((name, fty)),
333                        }
334                    }
335                    // Whatever is left of the accumulator is a singleton: `State(charged=0,
336                    // refused=0)` is one row of two columns, which is the relational shape of a
337                    // state that is not a collection. A fold whose every field is a collection
338                    // leaves nothing here and gets no such table.
339                    if !scalars.is_empty() {
340                        push(
341                            &mut tables,
342                            &mut taken,
343                            Table {
344                                name: role.name.clone(),
345                                columns: scalars
346                                    .iter()
347                                    .map(|(n, t)| column(n.clone(), t, types))
348                                    .collect(),
349                                source: Source::State(base.clone()),
350                                cardinality: Cardinality::One,
351                                element: Arc::from(ty.to_string()),
352                            },
353                        );
354                    }
355                }
356            }
357        }
358
359        // Derived signals, which is where a maintained arrangement becomes a table. The page is
360        // excluded by its type rather than by its name: `Html` is not a relation.
361        let by_op: BTreeMap<&str, OpId> = plan
362            .signals
363            .iter()
364            .map(|(n, id)| (n.as_ref(), *id))
365            .collect();
366        let folds: BTreeSet<&str> = placed
367            .roles
368            .states
369            .iter()
370            .map(|s| s.name.as_ref())
371            .collect();
372        for (name, &sig) in &placed.graph.by_name {
373            let Some(&op) = by_op.get(name.as_ref()) else {
374                continue;
375            };
376            if plan.nodes[op].per_session {
377                continue;
378            }
379            // The accumulator is not a table: its collections and its scalars are, and they are
380            // above. A `Signal[State]` here would be one row whose collection fields are rendered
381            // as JSON — the same data, in the shape nothing can query.
382            if folds.contains(name.as_ref()) {
383                continue;
384            }
385            let ty = resolve(
386                &crate::signal::signal_elem(&placed.graph.node(sig).ty),
387                types,
388            );
389            let t = match collection_elem(&ty, types) {
390                Some(elem) => table(
391                    name.clone(),
392                    &elem,
393                    types,
394                    Source::View(op),
395                    Cardinality::Many,
396                ),
397                // A record or a scalar signal is one row. A `Signal[State]` is neither useful nor
398                // harmful here — its fields are already base tables — so a name already taken
399                // wins, which `push` decides.
400                None if model_fields(&ty, types).is_some() || scalar(&ty).is_some() => {
401                    table(name.clone(), &ty, types, Source::View(op), Cardinality::One)
402                }
403                None => continue,
404            };
405            push(&mut tables, &mut taken, t);
406        }
407
408        tables.sort_by(|a, b| a.name.cmp(&b.name));
409        tables.push(Table {
410            name: Arc::from(Schema::CATALOGUE),
411            columns: [
412                "table_name",
413                "column_name",
414                "data_type",
415                "nullable",
416                "position",
417            ]
418            .iter()
419            .enumerate()
420            .map(|(i, n)| Column {
421                name: Arc::from(*n),
422                ty: if i == 4 {
423                    SqlTy::Bigint
424                } else if i == 3 {
425                    SqlTy::Boolean
426                } else {
427                    SqlTy::Text
428                },
429                nullable: false,
430            })
431            .collect(),
432            source: Source::Catalogue,
433            cardinality: Cardinality::Many,
434            element: Arc::from("Column"),
435        });
436        Schema { tables }
437    }
438
439    /// The catalogue's own rows: this schema, described.
440    pub fn catalogue_rows(&self) -> Vec<Vec<Cell>> {
441        let mut rows = Vec::new();
442        for t in &self.tables {
443            for (i, c) in t.columns.iter().enumerate() {
444                rows.push(vec![
445                    Some(Datum::Text(t.name.to_string())),
446                    Some(Datum::Text(c.name.to_string())),
447                    Some(Datum::Text(c.ty.name().to_string())),
448                    Some(Datum::Boolean(c.nullable)),
449                    Some(Datum::Bigint(i as i64 + 1)),
450                ]);
451            }
452        }
453        rows
454    }
455
456    /// The schema as `CREATE TABLE` statements, for `beck explain sql`.
457    ///
458    /// Nothing executes this — there is no database to execute it against, and saying so is the
459    /// point. It is the shape a person needs in order to write the query they were going to write.
460    pub fn ddl(&self) -> String {
461        let mut out = String::new();
462        for t in &self.tables {
463            let what = match &t.source {
464                Source::State(path) if path.is_empty() => "the accumulator".to_string(),
465                Source::State(path) => format!("state.{}", join(path)),
466                Source::View(op) => format!("plan operator {op}, maintained and shared"),
467                Source::Catalogue => "this schema".to_string(),
468            };
469            let _ = writeln!(
470                out,
471                "-- {} of {}, from {what}",
472                match t.cardinality {
473                    Cardinality::Many => "the elements",
474                    Cardinality::One => "one row",
475                },
476                t.element
477            );
478            let _ = writeln!(out, "create table {} (", quote_ident(&t.name));
479            let n = t.columns.len();
480            for (i, c) in t.columns.iter().enumerate() {
481                let _ = writeln!(
482                    out,
483                    "    {:<20} {}{}{}",
484                    quote_ident(&c.name),
485                    c.ty.name(),
486                    if c.nullable { "" } else { " not null" },
487                    if i + 1 == n { "" } else { "," }
488                );
489            }
490            let _ = writeln!(out, ");");
491        }
492        out
493    }
494}
495
496/// The words this SQL reads as syntax rather than as a name.
497///
498/// A Beck field may be called `distinct` or `order` — `corpus/17-derived.beck` has a `Summary` with
499/// a field called `distinct` — and a column whose name has to be quoted is a column a person must
500/// be *told* to quote. So the DDL quotes it, which is the one place they will see it written down.
501const RESERVED: &[&str] = &[
502    "abort", "and", "as", "asc", "begin", "by", "commit", "count", "desc", "discard", "distinct",
503    "end", "false", "from", "group", "is", "limit", "not", "null", "offset", "or", "order",
504    "rollback", "select", "set", "start", "table", "true", "where",
505];
506
507/// A name as it has to be written in this SQL: bare when it can be, quoted when it cannot.
508pub fn quote_ident(name: &str) -> String {
509    let plain = !name.is_empty()
510        && !name.starts_with(|c: char| c.is_ascii_digit())
511        && name
512            .chars()
513            .all(|c| c == '_' || c.is_ascii_lowercase() || c.is_ascii_digit());
514    if plain && !RESERVED.contains(&name) {
515        return name.to_string();
516    }
517    format!("\"{}\"", name.replace('"', "\"\""))
518}
519
520fn join(path: &[Arc<str>]) -> String {
521    path.iter()
522        .map(|p| p.to_string())
523        .collect::<Vec<_>>()
524        .join(".")
525}
526
527/// Add a table unless its name is taken. First wins, and the order is base tables then derived
528/// ones, so a signal named after the fold does not shadow the fold's own collections.
529fn push(tables: &mut Vec<Table>, taken: &mut BTreeSet<Arc<str>>, t: Table) {
530    if taken.insert(t.name.clone()) {
531        tables.push(t);
532    }
533}
534
535fn table(
536    name: Arc<str>,
537    elem: &Ty,
538    types: &BTreeMap<Arc<str>, TyDecl>,
539    source: Source,
540    cardinality: Cardinality,
541) -> Table {
542    let elem = resolve(elem, types);
543    let columns = match model_fields(&elem, types) {
544        Some(fields) => fields
545            .into_iter()
546            .map(|(n, t)| column(n, &t, types))
547            .collect(),
548        // A collection of scalars, or of anything else: one column, and the row is the element.
549        None => vec![column(Arc::from("value"), &elem, types)],
550    };
551    Table {
552        name,
553        columns,
554        source,
555        cardinality,
556        element: Arc::from(elem.to_string()),
557    }
558}
559
560fn column(name: Arc<str>, ty: &Ty, types: &BTreeMap<Arc<str>, TyDecl>) -> Column {
561    let (ty, nullable) = sql_ty(ty, types);
562    Column { name, ty, nullable }
563}
564
565/// The SQL type of a Beck type, and whether it can be null.
566fn sql_ty(ty: &Ty, types: &BTreeMap<Arc<str>, TyDecl>) -> (SqlTy, bool) {
567    let ty = resolve(ty, types);
568    if let Ty::Con(n, args) = &ty {
569        if n.as_ref() == Ty::OPTION && args.len() == 1 {
570            return (sql_ty(&args[0], types).0, true);
571        }
572    }
573    (scalar(&ty).unwrap_or(SqlTy::Text), false)
574}
575
576/// The SQL type of a Beck *scalar*, or nothing if it is not one.
577fn scalar(ty: &Ty) -> Option<SqlTy> {
578    match ty {
579        Ty::Con(n, args) if args.is_empty() => match n.as_ref() {
580            Ty::INT => Some(SqlTy::Bigint),
581            Ty::FLOAT => Some(SqlTy::Double),
582            Ty::BOOL => Some(SqlTy::Boolean),
583            Ty::STR => Some(SqlTy::Text),
584            _ => None,
585        },
586        _ => None,
587    }
588}
589
590/// See through aliases and newtypes, which are the two declarations that mean "this type, spelled
591/// differently". A `model` and a `union` are not resolved: they are the thing itself.
592fn resolve(ty: &Ty, types: &BTreeMap<Arc<str>, TyDecl>) -> Ty {
593    let mut ty = ty.clone();
594    // Bounded because a `type` alias can be recursive in a program that did not compile, and this
595    // runs over whatever it is handed.
596    for _ in 0..16 {
597        let Ty::Con(name, args) = &ty else { return ty };
598        let next = match types.get(name) {
599            Some(TyDecl::Newtype { params, inner, .. }) => substitute(inner, params, args),
600            Some(TyDecl::Alias { params, ty: t, .. }) => substitute(t, params, args),
601            _ => return ty,
602        };
603        ty = next;
604    }
605    ty
606}
607
608fn substitute(ty: &Ty, params: &[Arc<str>], args: &[Ty]) -> Ty {
609    if params.is_empty() {
610        return ty.clone();
611    }
612    match ty {
613        Ty::Con(n, inner) if inner.is_empty() => match params.iter().position(|p| p == n) {
614            Some(i) if i < args.len() => args[i].clone(),
615            _ => ty.clone(),
616        },
617        Ty::Con(n, inner) => Ty::Con(
618            n.clone(),
619            inner.iter().map(|t| substitute(t, params, args)).collect(),
620        ),
621        _ => ty.clone(),
622    }
623}
624
625/// The element type of a `list[T]` or a `Map[K, V]`, or nothing.
626fn collection_elem(ty: &Ty, types: &BTreeMap<Arc<str>, TyDecl>) -> Option<Ty> {
627    match resolve(ty, types) {
628        Ty::Con(n, args) if n.as_ref() == Ty::LIST && args.len() == 1 => Some(args[0].clone()),
629        Ty::Con(n, args) if n.as_ref() == Ty::MAP && args.len() == 2 => Some(args[1].clone()),
630        _ => None,
631    }
632}
633
634/// A `model`'s fields, in the order they were written.
635///
636/// Declared order rather than name order, which is the one place this disagrees with the run-time
637/// representation ([`crate::core::Fields`] sorts by name, and `docs/50` §50.5 pinned that). Columns
638/// are read by name, so the disagreement costs nothing and the person reading `select *` gets their
639/// own declaration back.
640fn model_fields(ty: &Ty, types: &BTreeMap<Arc<str>, TyDecl>) -> Option<Vec<(Arc<str>, Ty)>> {
641    let Ty::Con(name, args) = resolve(ty, types) else {
642        return None;
643    };
644    match types.get(&name) {
645        Some(TyDecl::Model { params, fields, .. }) if !fields.is_empty() => Some(
646            fields
647                .iter()
648                .map(|(n, t)| (n.clone(), substitute(t, params, &args)))
649                .collect(),
650        ),
651        _ => None,
652    }
653}
654
655/// The elements of a collection value, in the order it holds them.
656pub fn elements(v: &Value) -> Vec<Value> {
657    match v {
658        Value::List(xs) => xs.as_ref().clone(),
659        Value::Map(m) => m.iter().map(|(_, v)| v.clone()).collect(),
660        other => vec![other.clone()],
661    }
662}
663
664/// Follow a path of field names into a value.
665pub fn at_path(v: &Value, path: &[Arc<str>]) -> Option<Value> {
666    let mut cur = v.clone();
667    for step in path {
668        let Value::Data(d) = &cur else { return None };
669        cur = d.fields.get(step)?.clone();
670    }
671    Some(cur)
672}
673
674// -------------------------------------------------------------------------------------------
675// The query
676// -------------------------------------------------------------------------------------------
677
678/// What a query asks for.
679///
680/// One table, because there is no join; and no expressions beyond a column, a literal and
681/// `count(*)`, because an expression language is what [`04`](../../../../../docs/04-compiler-architecture.md)
682/// §4.2 says the `Query` sub-language is *for* and this is not it.
683#[derive(Clone, Debug)]
684pub struct Select {
685    pub items: Vec<Item>,
686    pub from: Option<String>,
687    pub filter: Vec<Vec<Cond>>,
688    pub order: Option<(String, bool)>,
689    pub limit: Option<usize>,
690    pub offset: usize,
691}
692
693#[derive(Clone, Debug)]
694pub enum Item {
695    All,
696    Column(String, Option<String>),
697    Count(Option<String>),
698    Literal(Datum, Option<String>),
699}
700
701#[derive(Clone, Debug)]
702pub struct Cond {
703    pub column: String,
704    pub op: CmpOp,
705    pub value: Option<Datum>,
706}
707
708#[derive(Clone, Copy, Debug, PartialEq, Eq)]
709pub enum CmpOp {
710    Eq,
711    Ne,
712    Lt,
713    Le,
714    Gt,
715    Ge,
716    Is,
717    IsNot,
718}
719
720/// A statement, which is a `select` or one of the two things a client says before it asks for
721/// anything.
722#[derive(Clone, Debug)]
723pub enum Stmt {
724    Select(Select),
725    /// `SET …` and `BEGIN`/`COMMIT`/`ROLLBACK`: acknowledged and ignored. A read model has nothing
726    /// to set and nothing to roll back, and a driver that opens a transaction out of habit should
727    /// not be refused for it.
728    Ignored(&'static str),
729}
730
731/// What a query answered.
732pub struct Answer {
733    pub columns: Vec<Column>,
734    pub rows: Vec<Vec<Cell>>,
735    /// The `CommandComplete` tag.
736    pub tag: String,
737}
738
739/// Why a query could not be answered. The message reaches the client verbatim.
740#[derive(Clone, Debug, PartialEq, Eq)]
741pub struct SqlError {
742    pub message: String,
743    /// The five-character SQLSTATE. A driver reads this; a person reads the message.
744    pub code: &'static str,
745}
746
747impl SqlError {
748    fn syntax(m: impl Into<String>) -> SqlError {
749        SqlError {
750            message: m.into(),
751            code: "42601",
752        }
753    }
754    fn no_table(m: impl Into<String>) -> SqlError {
755        SqlError {
756            message: m.into(),
757            code: "42P01",
758        }
759    }
760    fn no_column(m: impl Into<String>) -> SqlError {
761        SqlError {
762            message: m.into(),
763            code: "42703",
764        }
765    }
766    fn unsupported(m: impl Into<String>) -> SqlError {
767        SqlError {
768            message: m.into(),
769            code: "0A000",
770        }
771    }
772}
773
774impl std::fmt::Display for SqlError {
775    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
776        f.write_str(&self.message)
777    }
778}
779
780impl std::error::Error for SqlError {}
781
782/// Where a table's rows come from. Implemented by whoever holds the running program.
783pub trait Rows {
784    /// Every row of one table, in the order the collection holds them.
785    fn scan(&self, table: &Table) -> Result<Vec<Value>, SqlError>;
786}
787
788impl Schema {
789    /// Parse and run one statement.
790    pub fn run(&self, sql: &str, rows: &dyn Rows) -> Result<Answer, SqlError> {
791        match parse(sql)? {
792            Stmt::Ignored(tag) => Ok(Answer {
793                columns: Vec::new(),
794                rows: Vec::new(),
795                tag: tag.to_string(),
796            }),
797            Stmt::Select(s) => self.select(&s, rows),
798        }
799    }
800
801    /// What a statement's result looks like, without running it. `Describe` needs this before
802    /// `Execute` has happened.
803    pub fn describe(&self, sql: &str) -> Result<Vec<Column>, SqlError> {
804        match parse(sql)? {
805            Stmt::Ignored(_) => Ok(Vec::new()),
806            Stmt::Select(s) => {
807                let table = self.resolve_from(&s)?;
808                Ok(self.project_columns(&s, table)?.0)
809            }
810        }
811    }
812
813    fn resolve_from(&self, s: &Select) -> Result<Option<&Table>, SqlError> {
814        match &s.from {
815            None => Ok(None),
816            Some(name) => match self.table(name) {
817                Some(t) => Ok(Some(t)),
818                None => Err(SqlError::no_table(format!(
819                    "there is no read model called \"{name}\". \
820                     `select table_name from {} group by`— no: this SQL has no group by; \
821                     `select * from {}` lists what there is",
822                    Schema::CATALOGUE,
823                    Schema::CATALOGUE
824                ))),
825            },
826        }
827    }
828
829    /// The columns a select produces, and how to build each from a source row.
830    fn project_columns(
831        &self,
832        s: &Select,
833        table: Option<&Table>,
834    ) -> Result<(Vec<Column>, Vec<Proj>), SqlError> {
835        let mut columns = Vec::new();
836        let mut proj = Vec::new();
837        for item in &s.items {
838            match item {
839                Item::All => {
840                    let Some(t) = table else {
841                        return Err(SqlError::syntax("`select *` needs a `from`"));
842                    };
843                    for (i, c) in t.columns.iter().enumerate() {
844                        columns.push(c.clone());
845                        proj.push(Proj::Column(i));
846                    }
847                }
848                Item::Column(name, alias) => {
849                    let Some(t) = table else {
850                        return Err(SqlError::no_column(format!(
851                            "there is no column \"{name}\" here, because there is no `from`"
852                        )));
853                    };
854                    let (i, c) = t.column(name).ok_or_else(|| {
855                        SqlError::no_column(format!(
856                            "\"{}\" has no column \"{name}\"; it has {}",
857                            t.name,
858                            names(&t.columns)
859                        ))
860                    })?;
861                    let mut c = c.clone();
862                    if let Some(a) = alias {
863                        c.name = Arc::from(a.as_str());
864                    }
865                    columns.push(c);
866                    proj.push(Proj::Column(i));
867                }
868                Item::Count(alias) => {
869                    columns.push(Column {
870                        name: Arc::from(alias.as_deref().unwrap_or("count")),
871                        ty: SqlTy::Bigint,
872                        nullable: false,
873                    });
874                    proj.push(Proj::Count);
875                }
876                Item::Literal(d, alias) => {
877                    columns.push(Column {
878                        name: Arc::from(alias.as_deref().unwrap_or("?column?")),
879                        ty: d.ty(),
880                        nullable: false,
881                    });
882                    proj.push(Proj::Literal(d.clone()));
883                }
884            }
885        }
886        Ok((columns, proj))
887    }
888
889    fn select(&self, s: &Select, rows_of: &dyn Rows) -> Result<Answer, SqlError> {
890        let table = self.resolve_from(s)?;
891        let (columns, proj) = self.project_columns(s, table)?;
892
893        // `select 1` and friends: one row, no table, and the four things a driver asks before it
894        // trusts a connection.
895        let Some(t) = table else {
896            let row: Vec<Cell> = proj
897                .iter()
898                .map(|p| match p {
899                    Proj::Literal(d) => Some(d.clone()),
900                    Proj::Count => Some(Datum::Bigint(1)),
901                    Proj::Column(_) => None,
902                })
903                .collect();
904            return Ok(Answer {
905                tag: "SELECT 1".to_string(),
906                columns,
907                rows: vec![row],
908            });
909        };
910
911        let mut rows: Vec<Vec<Cell>> = match &t.source {
912            Source::Catalogue => self.catalogue_rows(),
913            _ => {
914                let values = rows_of.scan(t)?;
915                match t.cardinality {
916                    Cardinality::Many => values.iter().map(|v| t.row(v)).collect(),
917                    // A singleton is one row even when the reader hands over the value inside a
918                    // one-element list, which is what `elements` does with a record.
919                    Cardinality::One => values.iter().take(1).map(|v| t.row(v)).collect(),
920                }
921            }
922        };
923
924        // Filter, then order, then offset and limit — the order SQL specifies, and the order that
925        // makes `limit` mean what a person expects.
926        for disjunction in &s.filter {
927            let mut tested = Vec::with_capacity(rows.len());
928            for row in rows {
929                if any_matches(t, disjunction, &row)? {
930                    tested.push(row);
931                }
932            }
933            rows = tested;
934        }
935        if let Some((name, asc)) = &s.order {
936            let (i, _) = t.column(name).ok_or_else(|| {
937                SqlError::no_column(format!(
938                    "cannot order \"{}\" by \"{name}\"; it has {}",
939                    t.name,
940                    names(&t.columns)
941                ))
942            })?;
943            // Stable, so the order the collection holds its elements in survives ties — which is
944            // the arrangement's key order, and therefore the order the page renders in.
945            rows.sort_by(|a, b| {
946                let o = compare(&a[i], &b[i]);
947                if *asc {
948                    o
949                } else {
950                    o.reverse()
951                }
952            });
953        }
954        if s.offset > 0 {
955            rows = rows.split_off(s.offset.min(rows.len()));
956        }
957        if let Some(n) = s.limit {
958            rows.truncate(n);
959        }
960
961        // `count(*)` collapses. Mixing it with a column would be a group-by, which this has none
962        // of, so it is refused at parse time rather than answered wrongly.
963        let out: Vec<Vec<Cell>> = if proj.iter().any(|p| matches!(p, Proj::Count)) {
964            vec![proj
965                .iter()
966                .map(|p| match p {
967                    Proj::Count => Some(Datum::Bigint(rows.len() as i64)),
968                    Proj::Literal(d) => Some(d.clone()),
969                    Proj::Column(_) => None,
970                })
971                .collect()]
972        } else {
973            rows.iter()
974                .map(|r| {
975                    proj.iter()
976                        .map(|p| match p {
977                            Proj::Column(i) => r[*i].clone(),
978                            Proj::Literal(d) => Some(d.clone()),
979                            Proj::Count => None,
980                        })
981                        .collect()
982                })
983                .collect()
984        };
985        Ok(Answer {
986            tag: format!("SELECT {}", out.len()),
987            columns,
988            rows: out,
989        })
990    }
991}
992
993enum Proj {
994    Column(usize),
995    Count,
996    Literal(Datum),
997}
998
999fn names(columns: &[Column]) -> String {
1000    columns
1001        .iter()
1002        .map(|c| format!("\"{}\"", c.name))
1003        .collect::<Vec<_>>()
1004        .join(", ")
1005}
1006
1007fn any_matches(t: &Table, conds: &[Cond], row: &[Cell]) -> Result<bool, SqlError> {
1008    for c in conds {
1009        let (i, _) = t.column(&c.column).ok_or_else(|| {
1010            SqlError::no_column(format!(
1011                "\"{}\" has no column \"{}\"; it has {}",
1012                t.name,
1013                c.column,
1014                names(&t.columns)
1015            ))
1016        })?;
1017        if matches_one(&row[i], c) {
1018            return Ok(true);
1019        }
1020    }
1021    Ok(false)
1022}
1023
1024fn matches_one(cell: &Cell, c: &Cond) -> bool {
1025    match c.op {
1026        CmpOp::Is => cell.is_none() == c.value.is_none() && (c.value.is_none() || equal(cell, c)),
1027        CmpOp::IsNot => {
1028            !(cell.is_none() == c.value.is_none() && (c.value.is_none() || equal(cell, c)))
1029        }
1030        // Three-valued logic, in the one place it shows up: a comparison against NULL is unknown,
1031        // and unknown is not true.
1032        _ => match (cell, &c.value) {
1033            (Some(a), Some(b)) => {
1034                let o = compare(&Some(a.clone()), &Some(b.clone()));
1035                match c.op {
1036                    CmpOp::Eq => o.is_eq(),
1037                    CmpOp::Ne => o.is_ne(),
1038                    CmpOp::Lt => o.is_lt(),
1039                    CmpOp::Le => o.is_le(),
1040                    CmpOp::Gt => o.is_gt(),
1041                    CmpOp::Ge => o.is_ge(),
1042                    CmpOp::Is | CmpOp::IsNot => false,
1043                }
1044            }
1045            _ => false,
1046        },
1047    }
1048}
1049
1050fn equal(cell: &Cell, c: &Cond) -> bool {
1051    compare(cell, &c.value).is_eq()
1052}
1053
1054/// NULLs sort last, as they do in Postgres for an ascending order.
1055fn compare(a: &Cell, b: &Cell) -> std::cmp::Ordering {
1056    use std::cmp::Ordering;
1057    match (a, b) {
1058        (None, None) => Ordering::Equal,
1059        (None, Some(_)) => Ordering::Greater,
1060        (Some(_), None) => Ordering::Less,
1061        (Some(x), Some(y)) => match (x, y) {
1062            (Datum::Bigint(p), Datum::Bigint(q)) => p.cmp(q),
1063            (Datum::Double(p), Datum::Double(q)) => p.partial_cmp(q).unwrap_or(Ordering::Equal),
1064            (Datum::Bigint(p), Datum::Double(q)) => {
1065                (*p as f64).partial_cmp(q).unwrap_or(Ordering::Equal)
1066            }
1067            (Datum::Double(p), Datum::Bigint(q)) => {
1068                p.partial_cmp(&(*q as f64)).unwrap_or(Ordering::Equal)
1069            }
1070            (Datum::Boolean(p), Datum::Boolean(q)) => p.cmp(q),
1071            (Datum::Text(p), Datum::Text(q)) => p.cmp(q),
1072            // Across kinds, compare the text. Nothing in a typed column reaches this; a literal
1073            // compared against a column of another type does.
1074            _ => x.text().cmp(&y.text()),
1075        },
1076    }
1077}
1078
1079// -------------------------------------------------------------------------------------------
1080// The parser
1081// -------------------------------------------------------------------------------------------
1082
1083#[derive(Clone, Debug, PartialEq)]
1084enum Tok {
1085    Word(String),
1086    Quoted(String),
1087    Str(String),
1088    Num(String),
1089    Sym(String),
1090}
1091
1092fn lex(sql: &str) -> Result<Vec<Tok>, SqlError> {
1093    let cs: Vec<char> = sql.chars().collect();
1094    let mut i = 0;
1095    let mut out = Vec::new();
1096    while i < cs.len() {
1097        let c = cs[i];
1098        if c.is_whitespace() {
1099            i += 1;
1100        } else if c == '-' && cs.get(i + 1) == Some(&'-') {
1101            while i < cs.len() && cs[i] != '\n' {
1102                i += 1;
1103            }
1104        } else if c == '_' || c.is_alphabetic() {
1105            let start = i;
1106            while i < cs.len() && (cs[i] == '_' || cs[i] == '$' || cs[i].is_alphanumeric()) {
1107                i += 1;
1108            }
1109            out.push(Tok::Word(cs[start..i].iter().collect()));
1110        } else if c.is_ascii_digit()
1111            || (c == '.' && cs.get(i + 1).is_some_and(char::is_ascii_digit))
1112        {
1113            let start = i;
1114            while i < cs.len() && (cs[i].is_ascii_digit() || cs[i] == '.') {
1115                i += 1;
1116            }
1117            out.push(Tok::Num(cs[start..i].iter().collect()));
1118        } else if c == '\'' {
1119            i += 1;
1120            let mut s = String::new();
1121            loop {
1122                match cs.get(i) {
1123                    None => return Err(SqlError::syntax("a string literal is not closed")),
1124                    // '' is an escaped quote, which is the only escape standard SQL has.
1125                    Some('\'') if cs.get(i + 1) == Some(&'\'') => {
1126                        s.push('\'');
1127                        i += 2;
1128                    }
1129                    Some('\'') => {
1130                        i += 1;
1131                        break;
1132                    }
1133                    Some(ch) => {
1134                        s.push(*ch);
1135                        i += 1;
1136                    }
1137                }
1138            }
1139            out.push(Tok::Str(s));
1140        } else if c == '"' {
1141            i += 1;
1142            let mut s = String::new();
1143            loop {
1144                match cs.get(i) {
1145                    None => return Err(SqlError::syntax("a quoted name is not closed")),
1146                    Some('"') if cs.get(i + 1) == Some(&'"') => {
1147                        s.push('"');
1148                        i += 2;
1149                    }
1150                    Some('"') => {
1151                        i += 1;
1152                        break;
1153                    }
1154                    Some(ch) => {
1155                        s.push(*ch);
1156                        i += 1;
1157                    }
1158                }
1159            }
1160            out.push(Tok::Quoted(s));
1161        } else {
1162            // Two-character operators first, so `<=` does not lex as `<` then `=`.
1163            let two: String = cs[i..(i + 2).min(cs.len())].iter().collect();
1164            if matches!(two.as_str(), "<=" | ">=" | "<>" | "!=") {
1165                out.push(Tok::Sym(two));
1166                i += 2;
1167            } else {
1168                out.push(Tok::Sym(c.to_string()));
1169                i += 1;
1170            }
1171        }
1172    }
1173    Ok(out)
1174}
1175
1176struct P {
1177    toks: Vec<Tok>,
1178    i: usize,
1179}
1180
1181impl P {
1182    fn peek(&self) -> Option<&Tok> {
1183        self.toks.get(self.i)
1184    }
1185
1186    /// The next token as a lower-cased keyword, if it is a bare word.
1187    fn keyword(&self) -> Option<String> {
1188        match self.peek() {
1189            Some(Tok::Word(w)) => Some(w.to_lowercase()),
1190            _ => None,
1191        }
1192    }
1193
1194    fn eat_keyword(&mut self, k: &str) -> bool {
1195        if self.keyword().as_deref() == Some(k) {
1196            self.i += 1;
1197            return true;
1198        }
1199        false
1200    }
1201
1202    fn eat_sym(&mut self, s: &str) -> bool {
1203        if self.peek() == Some(&Tok::Sym(s.to_string())) {
1204            self.i += 1;
1205            return true;
1206        }
1207        false
1208    }
1209
1210    /// An identifier: a bare word, case-folded the way an unquoted SQL name is, or a quoted one
1211    /// taken exactly as written. Beck names are lower-case, so folding down is what matches.
1212    fn name(&mut self) -> Option<String> {
1213        match self.peek().cloned() {
1214            Some(Tok::Word(w)) => {
1215                self.i += 1;
1216                Some(w.to_lowercase())
1217            }
1218            Some(Tok::Quoted(w)) => {
1219                self.i += 1;
1220                Some(w)
1221            }
1222            _ => None,
1223        }
1224    }
1225
1226    fn literal(&mut self) -> Option<Option<Datum>> {
1227        match self.peek().cloned() {
1228            Some(Tok::Str(s)) => {
1229                self.i += 1;
1230                Some(Some(Datum::Text(s)))
1231            }
1232            Some(Tok::Num(n)) => {
1233                self.i += 1;
1234                Some(Some(match n.parse::<i64>() {
1235                    Ok(i) => Datum::Bigint(i),
1236                    Err(_) => Datum::Double(n.parse::<f64>().unwrap_or(0.0)),
1237                }))
1238            }
1239            Some(Tok::Sym(s)) if s == "-" => {
1240                self.i += 1;
1241                match self.literal() {
1242                    Some(Some(Datum::Bigint(i))) => Some(Some(Datum::Bigint(-i))),
1243                    Some(Some(Datum::Double(f))) => Some(Some(Datum::Double(-f))),
1244                    _ => None,
1245                }
1246            }
1247            Some(Tok::Word(w)) => match w.to_lowercase().as_str() {
1248                "true" => {
1249                    self.i += 1;
1250                    Some(Some(Datum::Boolean(true)))
1251                }
1252                "false" => {
1253                    self.i += 1;
1254                    Some(Some(Datum::Boolean(false)))
1255                }
1256                "null" => {
1257                    self.i += 1;
1258                    Some(None)
1259                }
1260                _ => None,
1261            },
1262            _ => None,
1263        }
1264    }
1265}
1266
1267/// Parse one statement.
1268pub fn parse(sql: &str) -> Result<Stmt, SqlError> {
1269    let toks = lex(sql)?;
1270    let mut p = P { toks, i: 0 };
1271    let head = p.keyword().unwrap_or_default();
1272    match head.as_str() {
1273        "select" => {
1274            p.i += 1;
1275            let s = select(&mut p)?;
1276            // A trailing `;` is a statement separator, and a second statement is not supported —
1277            // saying so beats answering the first and dropping the rest.
1278            p.eat_sym(";");
1279            if p.peek().is_some() {
1280                return Err(SqlError::unsupported(
1281                    "one statement per query: this SQL has no multi-statement form",
1282                ));
1283            }
1284            Ok(Stmt::Select(s))
1285        }
1286        "set" => Ok(Stmt::Ignored("SET")),
1287        "begin" | "start" => Ok(Stmt::Ignored("BEGIN")),
1288        "commit" | "end" => Ok(Stmt::Ignored("COMMIT")),
1289        "rollback" | "abort" => Ok(Stmt::Ignored("ROLLBACK")),
1290        "discard" => Ok(Stmt::Ignored("DISCARD ALL")),
1291        "" => Err(SqlError::syntax("an empty query")),
1292        other => Err(SqlError::unsupported(format!(
1293            "a read model is read-only and this SQL is a subset: `{other}` is not one of \
1294             select, set, begin, commit, rollback"
1295        ))),
1296    }
1297}
1298
1299fn select(p: &mut P) -> Result<Select, SqlError> {
1300    // `distinct` would need a comparison over whole rows and nothing has asked for one.
1301    if p.eat_keyword("distinct") {
1302        return Err(SqlError::unsupported(
1303            "`distinct` is not in this SQL subset",
1304        ));
1305    }
1306    let mut items = Vec::new();
1307    loop {
1308        items.push(item(p)?);
1309        if !p.eat_sym(",") {
1310            break;
1311        }
1312    }
1313    if items.iter().filter(|i| matches!(i, Item::Count(_))).count() > 0
1314        && items
1315            .iter()
1316            .any(|i| matches!(i, Item::All | Item::Column(_, _)))
1317    {
1318        return Err(SqlError::unsupported(
1319            "`count(*)` beside a column would need a `group by`, and this SQL has none",
1320        ));
1321    }
1322
1323    let mut from = None;
1324    if p.eat_keyword("from") {
1325        from = Some(
1326            p.name()
1327                .ok_or_else(|| SqlError::syntax("`from` wants a table name"))?,
1328        );
1329    }
1330
1331    let mut filter = Vec::new();
1332    if p.eat_keyword("where") {
1333        filter = where_clause(p)?;
1334    }
1335
1336    let mut order = None;
1337    if p.eat_keyword("order") {
1338        if !p.eat_keyword("by") {
1339            return Err(SqlError::syntax("`order` wants `by`"));
1340        }
1341        let col = p
1342            .name()
1343            .ok_or_else(|| SqlError::syntax("`order by` wants a column"))?;
1344        let asc = if p.eat_keyword("desc") {
1345            false
1346        } else {
1347            p.eat_keyword("asc");
1348            true
1349        };
1350        order = Some((col, asc));
1351    }
1352
1353    let mut limit = None;
1354    let mut offset = 0;
1355    loop {
1356        if p.eat_keyword("limit") {
1357            match p.literal() {
1358                Some(Some(Datum::Bigint(n))) if n >= 0 => limit = Some(n as usize),
1359                _ => return Err(SqlError::syntax("`limit` wants a whole number")),
1360            }
1361        } else if p.eat_keyword("offset") {
1362            match p.literal() {
1363                Some(Some(Datum::Bigint(n))) if n >= 0 => offset = n as usize,
1364                _ => return Err(SqlError::syntax("`offset` wants a whole number")),
1365            }
1366        } else {
1367            break;
1368        }
1369    }
1370
1371    Ok(Select {
1372        items,
1373        from,
1374        filter,
1375        order,
1376        limit,
1377        offset,
1378    })
1379}
1380
1381fn item(p: &mut P) -> Result<Item, SqlError> {
1382    if p.eat_sym("*") {
1383        return Ok(Item::All);
1384    }
1385    if let Some(lit) = p.literal() {
1386        let d = lit.ok_or_else(|| SqlError::unsupported("`select null` has no column type"))?;
1387        return Ok(Item::Literal(d, alias(p)));
1388    }
1389    let name = p
1390        .name()
1391        .ok_or_else(|| SqlError::syntax("a select list wants a column, `*`, or a literal"))?;
1392    if p.eat_sym("(") {
1393        // The three zero-argument functions a client asks before it trusts a connection, plus the
1394        // only aggregate this subset has.
1395        let f = match name.as_str() {
1396            "count" => {
1397                if !p.eat_sym("*") {
1398                    return Err(SqlError::unsupported(
1399                        "`count` counts rows here: `count(*)` is the only form",
1400                    ));
1401                }
1402                Item::Count(None)
1403            }
1404            "version" => Item::Literal(Datum::Text(version()), None),
1405            "current_database" | "current_schema" | "current_catalog" => {
1406                Item::Literal(Datum::Text("beck".into()), None)
1407            }
1408            other => {
1409                return Err(SqlError::unsupported(format!(
1410                    "`{other}(…)` is not a function this read model has"
1411                )))
1412            }
1413        };
1414        if !p.eat_sym(")") {
1415            return Err(SqlError::syntax("a call is not closed"));
1416        }
1417        let a = alias(p);
1418        return Ok(match f {
1419            Item::Count(_) => Item::Count(a),
1420            Item::Literal(d, _) => Item::Literal(d, a.or(Some(name))),
1421            other => other,
1422        });
1423    }
1424    Ok(Item::Column(name.clone(), alias(p)))
1425}
1426
1427fn alias(p: &mut P) -> Option<String> {
1428    if p.eat_keyword("as") {
1429        return p.name();
1430    }
1431    // A bare alias, but not one of the words that ends a select item.
1432    match p.keyword().as_deref() {
1433        Some("from") | Some("where") | Some("order") | Some("limit") | Some("offset")
1434        | Some("as") | None => None,
1435        Some(_) => p.name(),
1436    }
1437}
1438
1439/// `a = 1 and b = 2 or c = 3`, as a conjunction of disjunctions.
1440///
1441/// `and` binds tighter than `or` in SQL, so the natural reading of the parse is the other way
1442/// round; this collects disjunctive groups and requires every group to have a true member, which
1443/// is the same thing said so the evaluator is a loop.
1444fn where_clause(p: &mut P) -> Result<Vec<Vec<Cond>>, SqlError> {
1445    let mut and_groups: Vec<Vec<Cond>> = vec![vec![cond(p)?]];
1446    loop {
1447        if p.eat_keyword("and") {
1448            and_groups.push(vec![cond(p)?]);
1449        } else if p.eat_keyword("or") {
1450            let c = cond(p)?;
1451            and_groups
1452                .last_mut()
1453                .expect("there is always one group")
1454                .push(c);
1455        } else {
1456            return Ok(and_groups);
1457        }
1458    }
1459}
1460
1461fn cond(p: &mut P) -> Result<Cond, SqlError> {
1462    if p.eat_sym("(") {
1463        return Err(SqlError::unsupported(
1464            "a parenthesised condition is not in this SQL subset",
1465        ));
1466    }
1467    let column = p
1468        .name()
1469        .ok_or_else(|| SqlError::syntax("`where` wants a column"))?;
1470    let op = if p.eat_keyword("is") {
1471        if p.eat_keyword("not") {
1472            CmpOp::IsNot
1473        } else {
1474            CmpOp::Is
1475        }
1476    } else if p.eat_sym("=") {
1477        CmpOp::Eq
1478    } else if p.eat_sym("<>") || p.eat_sym("!=") {
1479        CmpOp::Ne
1480    } else if p.eat_sym("<=") {
1481        CmpOp::Le
1482    } else if p.eat_sym(">=") {
1483        CmpOp::Ge
1484    } else if p.eat_sym("<") {
1485        CmpOp::Lt
1486    } else if p.eat_sym(">") {
1487        CmpOp::Gt
1488    } else {
1489        return Err(SqlError::unsupported(format!(
1490            "the comparisons here are =, <>, <, <=, >, >= and `is null`; \
1491             \"{column}\" is followed by none of them"
1492        )));
1493    };
1494    let value = p
1495        .literal()
1496        .ok_or_else(|| SqlError::syntax(format!("\"{column}\" is compared against nothing")))?;
1497    Ok(Cond { column, op, value })
1498}
1499
1500/// What `select version()` answers.
1501///
1502/// It names Beck rather than pretending to be Postgres. A client that branches on this string is
1503/// better off failing on a name it does not know than succeeding on a version it will be wrong
1504/// about — and the `pg` prefix is there because a driver that parses this expects to find one.
1505pub fn version() -> String {
1506    format!(
1507        "PostgreSQL 15.0 (beck {}) — a read model, not a database",
1508        env!("CARGO_PKG_VERSION")
1509    )
1510}
1511
1512#[cfg(test)]
1513mod tests {
1514    use super::*;
1515
1516    fn ok(sql: &str) -> Select {
1517        match parse(sql).expect("parses") {
1518            Stmt::Select(s) => s,
1519            other => panic!("not a select: {other:?}"),
1520        }
1521    }
1522
1523    #[test]
1524    fn a_select_is_case_folded_and_a_quoted_name_is_not() {
1525        let s = ok("SELECT Text FROM Todos");
1526        assert_eq!(s.from.as_deref(), Some("todos"));
1527        assert!(matches!(&s.items[0], Item::Column(c, _) if c == "text"));
1528        let s = ok(r#"select "Text" from "Todos""#);
1529        assert_eq!(s.from.as_deref(), Some("Todos"));
1530        assert!(matches!(&s.items[0], Item::Column(c, _) if c == "Text"));
1531    }
1532
1533    #[test]
1534    fn and_binds_tighter_than_or() {
1535        // `a or b and c` is `(a or b) and c` — two groups, the first with two members.
1536        let s = ok("select * from t where a = 1 or b = 2 and c = 3");
1537        assert_eq!(s.filter.len(), 2);
1538        assert_eq!(s.filter[0].len(), 2);
1539        assert_eq!(s.filter[1].len(), 1);
1540    }
1541
1542    #[test]
1543    fn a_negative_literal_is_one_number() {
1544        let s = ok("select * from t where n < -3");
1545        assert_eq!(s.filter[0][0].value, Some(Datum::Bigint(-3)));
1546    }
1547
1548    #[test]
1549    fn an_escaped_quote_is_one_character() {
1550        let s = ok("select * from t where name = 'it''s'");
1551        assert_eq!(s.filter[0][0].value, Some(Datum::Text("it's".to_string())));
1552    }
1553
1554    #[test]
1555    fn a_write_is_refused_by_name() {
1556        let e = parse("insert into todos values (1)").expect_err("refused");
1557        assert_eq!(e.code, "0A000");
1558        assert!(e.message.contains("read-only"), "{}", e.message);
1559    }
1560
1561    #[test]
1562    fn count_beside_a_column_is_refused_rather_than_answered() {
1563        let e = parse("select id, count(*) from todos").expect_err("refused");
1564        assert!(e.message.contains("group by"), "{}", e.message);
1565    }
1566
1567    #[test]
1568    fn a_second_statement_is_refused() {
1569        assert!(parse("select 1; select 2").is_err());
1570    }
1571
1572    #[test]
1573    fn nulls_sort_last_and_compare_as_unknown() {
1574        let c = Cond {
1575            column: "x".into(),
1576            op: CmpOp::Eq,
1577            value: Some(Datum::Bigint(1)),
1578        };
1579        assert!(!matches_one(&None, &c));
1580        let is_null = Cond {
1581            column: "x".into(),
1582            op: CmpOp::Is,
1583            value: None,
1584        };
1585        assert!(matches_one(&None, &is_null));
1586        assert!(!matches_one(&Some(Datum::Bigint(1)), &is_null));
1587        assert!(compare(&None, &Some(Datum::Bigint(1))).is_gt());
1588    }
1589}