Skip to main content

nedb_engine/
sqlselect.rs

1// SPDX-FileCopyrightText: 2026 INTERCHAINED LLC
2// SPDX-License-Identifier: BUSL-1.1
3// NEDB · © 2026 INTERCHAINED LLC × Eth-Interchained × Vex (Claude Opus 5)
4
5//! A real SQL `SELECT` engine — expressions, aliases, `CASE`, functions, joins.
6//!
7//! # Why this module exists
8//!
9//! The pgwire layer translates SQL text into NQL text. That works beautifully
10//! for `SELECT col FROM t WHERE x = 1`, and it cannot be stretched any
11//! further: NQL has no expressions, no table aliases, no `CASE`, no scalar
12//! functions and no joins. Those are not missing features of the translation —
13//! they are things the target language cannot say.
14//!
15//! And they are exactly what catalogue introspection is made of. `psql`'s
16//! `\dt` is one statement containing a two-table `LEFT JOIN`, a nine-branch
17//! `CASE`, two scalar function calls, four qualified column references, an
18//! `IN` list, a `!~` regex and `ORDER BY 1,2`. Every one of those has to work
19//! or the command does not.
20//!
21//! So this is a small but genuine SQL evaluator: lexer, parser, expression
22//! evaluator, nested-loop join. It operates over rows supplied by a callback,
23//! which is what lets the same engine serve synthesised catalogue relations
24//! today and stored collections later.
25//!
26//! # What it is NOT
27//!
28//! It is not a query planner and does not pretend to be. The join is a nested
29//! loop, which is honest for catalogue relations (tens of rows) and would be
30//! wrong to point at a large collection without an index strategy. That
31//! boundary is enforced by the caller, not hidden here.
32//!
33//! # The rule this module follows
34//!
35//! Anything it cannot evaluate is REFUSED with an error naming the construct.
36//! It never guesses. A catalogue query that silently returns the wrong rows
37//! produces an empty or wrong table list, and a wrong table list is
38//! indistinguishable from a correct one until somebody's data appears to be
39//! missing.
40
41use crate::sqljoin::{self, JoinExec, Strategy};
42use crate::sqlplan::{Plan, Stage};
43use crate::sqlpush::Pushdown;
44
45use anyhow::{bail, Result};
46use serde_json::{Map, Value};
47
48// ─────────────────────────────────────────────────────────────────────────────
49// Phase 1 — the lexer
50// ─────────────────────────────────────────────────────────────────────────────
51
52#[derive(Debug, Clone, PartialEq)]
53pub enum Tok {
54    /// A bare identifier or keyword, with its canonical UPPERCASE form and the
55    /// raw spelling. Both are kept for the same reason NQL keeps both: a
56    /// column may legitimately be called `count` or `value`, and folding case
57    /// at the lexer would look up a key the data does not have.
58    Word { upper: String, raw: String },
59    /// A `"double quoted"` identifier. Case is significant and it is NEVER a
60    /// keyword — `"select"` is a column named select.
61    Quoted(String),
62    /// A `'single quoted'` string literal, with `''` already collapsed.
63    Str(String),
64    Num(f64),
65    Op(String),
66    Punct(char),
67    Eof,
68}
69
70impl Tok {
71    fn is_kw(&self, kw: &str) -> bool {
72        matches!(self, Tok::Word { upper, .. } if upper == kw)
73    }
74    /// The identifier text, for a token usable as a name.
75    #[allow(dead_code)] // kept as the counterpart to `is_kw`; used by earlier phases
76    fn ident(&self) -> Option<String> {
77        match self {
78            Tok::Word { raw, .. } => Some(raw.clone()),
79            Tok::Quoted(s) => Some(s.clone()),
80            _ => None,
81        }
82    }
83}
84
85/// Operators, longest first. Order is load-bearing: `!~*` must be matched
86/// before `!~`, which must be matched before `!=`, or each longer operator
87/// tokenises as a shorter one plus garbage.
88const OPERATORS: &[&str] = &[
89    "!~*", "!~", "~*", "<>", "!=", ">=", "<=", "||", "::",
90    "=", "<", ">", "~", "+", "-", "*", "/", "%",
91];
92
93pub fn lex(src: &str) -> Result<Vec<Tok>> {
94    let b: Vec<char> = src.chars().collect();
95    let mut out = vec![];
96    let mut i = 0usize;
97
98    while i < b.len() {
99        let c = b[i];
100
101        // whitespace
102        if c.is_whitespace() {
103            i += 1;
104            continue;
105        }
106
107        // `-- line comment`
108        if c == '-' && b.get(i + 1) == Some(&'-') {
109            while i < b.len() && b[i] != '\n' {
110                i += 1;
111            }
112            continue;
113        }
114
115        // `/* block comment */`, which SQL allows to nest.
116        if c == '/' && b.get(i + 1) == Some(&'*') {
117            let mut depth = 1usize;
118            i += 2;
119            while i < b.len() && depth > 0 {
120                if b[i] == '/' && b.get(i + 1) == Some(&'*') {
121                    depth += 1;
122                    i += 2;
123                } else if b[i] == '*' && b.get(i + 1) == Some(&'/') {
124                    depth -= 1;
125                    i += 2;
126                } else {
127                    i += 1;
128                }
129            }
130            if depth > 0 {
131                bail!("unterminated /* comment");
132            }
133            continue;
134        }
135
136        // 'string literal', where '' is one literal quote.
137        if c == '\'' {
138            i += 1;
139            let mut s = String::new();
140            loop {
141                match b.get(i) {
142                    None => bail!("unterminated string literal"),
143                    Some('\'') if b.get(i + 1) == Some(&'\'') => {
144                        s.push('\'');
145                        i += 2;
146                    }
147                    Some('\'') => {
148                        i += 1;
149                        break;
150                    }
151                    Some(ch) => {
152                        s.push(*ch);
153                        i += 1;
154                    }
155                }
156            }
157            out.push(Tok::Str(s));
158            continue;
159        }
160
161        // E'escape string' — Postgres spells a newline this way inside
162        // catalogue queries (`array_to_string(d.datacl, E'\n')`).
163        if (c == 'E' || c == 'e') && b.get(i + 1) == Some(&'\'') {
164            i += 2;
165            let mut s = String::new();
166            loop {
167                match b.get(i) {
168                    None => bail!("unterminated E'' string literal"),
169                    Some('\\') => {
170                        // Only the escapes that appear in real catalogue SQL.
171                        // An unknown escape keeps its literal character rather
172                        // than being dropped, so nothing silently vanishes.
173                        let esc = b.get(i + 1).copied().unwrap_or('\\');
174                        s.push(match esc {
175                            'n' => '\n',
176                            't' => '\t',
177                            'r' => '\r',
178                            '0' => '\0',
179                            other => other,
180                        });
181                        i += 2;
182                    }
183                    Some('\'') if b.get(i + 1) == Some(&'\'') => {
184                        s.push('\'');
185                        i += 2;
186                    }
187                    Some('\'') => {
188                        i += 1;
189                        break;
190                    }
191                    Some(ch) => {
192                        s.push(*ch);
193                        i += 1;
194                    }
195                }
196            }
197            out.push(Tok::Str(s));
198            continue;
199        }
200
201        // "quoted identifier", where "" is one literal quote.
202        if c == '"' {
203            i += 1;
204            let mut s = String::new();
205            loop {
206                match b.get(i) {
207                    None => bail!("unterminated quoted identifier"),
208                    Some('"') if b.get(i + 1) == Some(&'"') => {
209                        s.push('"');
210                        i += 2;
211                    }
212                    Some('"') => {
213                        i += 1;
214                        break;
215                    }
216                    Some(ch) => {
217                        s.push(*ch);
218                        i += 1;
219                    }
220                }
221            }
222            out.push(Tok::Quoted(s));
223            continue;
224        }
225
226        // number — digits, an optional fraction, an optional exponent.
227        if c.is_ascii_digit()
228            || (c == '.' && b.get(i + 1).map(|d| d.is_ascii_digit()).unwrap_or(false))
229        {
230            let start = i;
231            while i < b.len() && (b[i].is_ascii_digit() || b[i] == '.') {
232                i += 1;
233            }
234            if i < b.len() && (b[i] == 'e' || b[i] == 'E') {
235                let save = i;
236                i += 1;
237                if i < b.len() && (b[i] == '+' || b[i] == '-') {
238                    i += 1;
239                }
240                if i < b.len() && b[i].is_ascii_digit() {
241                    while i < b.len() && b[i].is_ascii_digit() {
242                        i += 1;
243                    }
244                } else {
245                    i = save; // `1e` is the number 1 followed by the name `e`
246                }
247            }
248            let text: String = b[start..i].iter().collect();
249            let n: f64 = text
250                .parse()
251                .map_err(|_| anyhow::anyhow!("not a number: {:?}", text))?;
252            out.push(Tok::Num(n));
253            continue;
254        }
255
256        // identifier / keyword. `$` is legal in a Postgres identifier.
257        if c.is_alphabetic() || c == '_' {
258            let start = i;
259            while i < b.len() && (b[i].is_alphanumeric() || b[i] == '_' || b[i] == '$') {
260                i += 1;
261            }
262            let raw: String = b[start..i].iter().collect();
263            out.push(Tok::Word { upper: raw.to_uppercase(), raw });
264            continue;
265        }
266
267        // operator — longest match wins.
268        let rest: String = b[i..].iter().take(3).collect();
269        if let Some(op) = OPERATORS.iter().find(|o| rest.starts_with(**o)) {
270            i += op.chars().count();
271            out.push(Tok::Op((*op).to_string()));
272            continue;
273        }
274
275        if matches!(c, '(' | ')' | ',' | ';' | '.' | '[' | ']') {
276            out.push(Tok::Punct(c));
277            i += 1;
278            continue;
279        }
280
281        // Refused rather than skipped. Skipping an unknown character is how a
282        // parser silently reads a different query than the one it was given.
283        bail!("unexpected character {:?} in SQL", c);
284    }
285
286    out.push(Tok::Eof);
287    Ok(out)
288}
289
290// ─────────────────────────────────────────────────────────────────────────────
291// Phase 2 — the AST
292// ─────────────────────────────────────────────────────────────────────────────
293
294#[derive(Debug, Clone, PartialEq)]
295pub enum Expr {
296    /// `nspname` or `n.nspname`. The qualifier is kept because a join makes
297    /// bare names ambiguous, and resolving an ambiguous name by guessing is
298    /// how a query silently reads the wrong table's column.
299    Column { qual: Option<String>, name: String },
300    Literal(Value),
301    /// `*` in `count(*)`, and in a bare select list.
302    Star,
303    /// `alias.*`
304    QualifiedStar(String),
305    Func { name: String, args: Vec<Expr> },
306    /// Both SQL spellings:
307    ///   simple   — `CASE x WHEN 'r' THEN 'table' ... ELSE ... END`
308    ///   searched — `CASE WHEN x = 'r' THEN 'table' ... ELSE ... END`
309    /// psql's `\dt` uses the simple form with nine branches.
310    Case {
311        operand: Option<Box<Expr>>,
312        whens: Vec<(Expr, Expr)>,
313        else_: Option<Box<Expr>>,
314    },
315    Binary { op: String, left: Box<Expr>, right: Box<Expr> },
316    Unary { op: String, expr: Box<Expr> },
317    /// `x [NOT] IN (a, b, c)`
318    InList { expr: Box<Expr>, list: Vec<Expr>, negated: bool },
319    /// `x IS [NOT] NULL`
320    IsNull { expr: Box<Expr>, negated: bool },
321    /// `x::type` — the cast is PARSED and then ignored at evaluation, because
322    /// this engine is dynamically typed. Ignoring it is safe for the shapes
323    /// catalogue SQL uses (`prattrs::int2[]`), and the alternative — refusing
324    /// every cast — would reject queries whose result the cast cannot change.
325    Cast { expr: Box<Expr>, ty: String },
326    /// `(SELECT ...)` used as a VALUE: one column, at most one row. Postgres's
327    /// `\dT` hinges on one (`(SELECT c.relkind = 'c' FROM pg_class c WHERE
328    /// c.oid = t.typrelid)`), and `\d <table>` on three.
329    Subquery(Box<Select>),
330    /// `[NOT] EXISTS (SELECT ...)` — never NULL, which is why it is its own
331    /// variant rather than `Subquery IS NOT NULL`.
332    Exists { query: Box<Select>, negated: bool },
333    /// `ARRAY(SELECT ...)` — the first column of every row, as one array.
334    /// `\dp`, `\dT+`, `\dD` and `\dy` all build one and hand it to
335    /// `array_to_string`.
336    ArrayQuery(Box<Select>),
337    /// `x [NOT] IN (SELECT ...)` — `InList` semantics over the first column.
338    InSubquery { expr: Box<Expr>, query: Box<Select>, negated: bool },
339    /// `x op ANY (...)` / `x op SOME (...)` / `x op ALL (...)`. The right side
340    /// evaluates to an array — an `ArrayQuery` when it was written as a
341    /// subquery — and `op` is applied element by element.
342    Quantified { op: String, left: Box<Expr>, all: bool, right: Box<Expr> },
343    /// `arr[i]` — one-based, as Postgres subscripts are.
344    Index { expr: Box<Expr>, index: Box<Expr> },
345    /// `ARRAY[a, b, c]` — an array literal.
346    ArrayLit(Vec<Expr>),
347    /// An aggregate call: `count(*)`, `array_agg(x ORDER BY y)`,
348    /// `string_agg(DISTINCT s, ',')`.
349    ///
350    /// A variant of its own rather than a `Func` whose name happens to be in a
351    /// list. "Is this an aggregate?" was a string comparison repeated at five
352    /// sites — the select-list check, the pushdown walker, the join-key
353    /// walker, the folder and the evaluator — and five copies of one rule is
354    /// five chances to disagree about it. It is now a question about SHAPE.
355    ///
356    /// It also carries the two modifiers only an aggregate has, and which
357    /// SQLAlchemy's primary-key reflection depends on:
358    /// `array_agg(CAST(attname AS TEXT) ORDER BY ord)` is meaningless without
359    /// the ordering — the array IS the column list, in key order.
360    Agg {
361        name: String,
362        args: Vec<Expr>,
363        /// Sorts the rows of the GROUP before the values are collected.
364        order_by: Vec<OrderBy>,
365        distinct: bool,
366    },
367}
368
369#[derive(Debug, Clone, PartialEq)]
370pub struct SelectItem {
371    pub expr: Expr,
372    /// The name the client sees. `None` means it is derived from the
373    /// expression, the way Postgres derives it.
374    pub alias: Option<String>,
375}
376
377#[derive(Debug, Clone, Copy, PartialEq, Eq)]
378pub enum JoinKind { Inner, Left, Right, Full, Cross }
379
380#[derive(Debug, Clone, PartialEq)]
381pub struct TableRef {
382    /// The table name as written, minus quoting. A `pg_catalog.` qualifier is
383    /// preserved here and resolved by the caller, because `information_schema`
384    /// table names collide with plausible user collection names.
385    ///
386    /// For a derived table this is the literal `(subquery)`, and for a table
387    /// function it is the function's bare name — both only ever shown in a
388    /// plan, never resolved as a relation.
389    pub name: String,
390    pub alias: Option<String>,
391    /// `FROM (SELECT ...) AS t` — the relation is the subquery's output.
392    /// psql's `\dd` is one seven-arm `UNION ALL` wrapped exactly this way.
393    pub sub: Option<Box<Select>>,
394    /// `FROM generate_series(0, n) s` / `FROM unnest(arr) AS t(x)` — a table
395    /// function with its arguments. Evaluated in the enclosing row's scope,
396    /// because psql writes `unnest(evttags)` over the OUTER row's column.
397    pub args: Option<Vec<Expr>>,
398    /// `AS t(x, y)` — column aliases for a derived table or table function.
399    pub col_aliases: Vec<String>,
400    /// `LATERAL (SELECT ...)` — the derived table may read the FROM items
401    /// before it, so it is re-evaluated once per row of those. psql's `\dP+`
402    /// sizes each partitioned table this way.
403    pub lateral: bool,
404    /// `FROM orders AS OF SYSTEM TIME 42` — read this relation at that NEDB
405    /// sequence instead of at the tip.
406    ///
407    /// It hangs on the TABLE rather than on the query for two reasons. It is
408    /// where PostgreSQL's own grammar would take it (`relation_expr`, the
409    /// production `table_ref` is built from), and it is the only placement that
410    /// can express the query worth having: one relation AS OF a past sequence
411    /// joined against another at the tip, which is how you ask what changed.
412    ///
413    /// A sequence, never a wall-clock time. NEDB's history is
414    /// sequence-addressed and never garbage-collected, so a seq is exact where
415    /// a timestamp would be approximate — the same refusal the translator has
416    /// always made, made in the same words.
417    pub as_of: Option<u64>,
418    /// `FROM orders VALID AS OF '2026-01-01'` — bi-temporal: what was believed
419    /// TRUE as of that date, as distinct from what the log SAID at a sequence.
420    /// A date string, because application-time validity is a wall-clock notion
421    /// where system time is a sequence.
422    pub valid_as_of: Option<String>,
423    /// `FROM orders SEARCH 'acme'` — full-text over the document's fields.
424    ///
425    /// Per-table like the others, which is the point: one relation searched
426    /// and another joined to it is a sentence SQL can now say.
427    pub search: Option<String>,
428    /// `FROM orders TRACE caused_by [REVERSE]` — the causal chain that produced
429    /// each row, walked over `caused_by` edges.
430    ///
431    /// The last two NQL verbs to arrive here, and the reason they were last is
432    /// that they are the two that CHANGE THE ROW SET rather than filter it: a
433    /// trace replaces each row with its chain. That is also why they belong on
434    /// the table rather than in the `WHERE` — the transform happens as the
435    /// relation is produced, and everything SQL does (join, subquery, set
436    /// operation, aggregate) then composes around the traced relation.
437    ///
438    /// Unreserved, by the same discipline as `SEARCH` and `VALID`: `TRACE`
439    /// starts a clause only when an identifier follows it, so `FROM orders
440    /// trace` still aliases the relation `trace`.
441    pub trace: Option<String>,
442    /// `REVERSE` on the trace — walk effects instead of causes.
443    pub trace_reverse: bool,
444    /// `FROM orders TRAVERSE ships_to` — one hop along a named relation,
445    /// replacing each row with its neighbours.
446    pub traverse: Option<String>,
447}
448
449impl TableRef {
450    /// A plain named relation.
451    pub fn named(name: impl Into<String>, alias: Option<String>) -> Self {
452        TableRef { name: name.into(), alias, sub: None, args: None, col_aliases: vec![], lateral: false, as_of: None, valid_as_of: None, search: None, trace: None, trace_reverse: false, traverse: None }
453    }
454
455    /// How this table's columns are addressed: the alias when given, else the
456    /// table's own bare name, which is what SQL says.
457    pub fn binding(&self) -> String {
458        self.alias.clone().unwrap_or_else(|| {
459            self.name.rsplit('.').next().unwrap_or(&self.name).to_string()
460        })
461    }
462}
463
464/// `UNION` / `INTERSECT` / `EXCEPT`.
465#[derive(Debug, Clone, Copy, PartialEq, Eq)]
466pub enum SetOp { Union, Intersect, Except }
467
468/// One further arm of a compound query: `<op> [ALL] SELECT ...`.
469#[derive(Debug, Clone, PartialEq)]
470pub struct SetArm {
471    pub op: SetOp,
472    pub all: bool,
473    pub query: Select,
474}
475
476#[derive(Debug, Clone, PartialEq)]
477pub struct Join {
478    pub kind: JoinKind,
479    pub table: TableRef,
480    pub on: Option<Expr>,
481}
482
483#[derive(Debug, Clone, Copy, PartialEq)]
484pub enum Dir { Asc, Desc }
485
486#[derive(Debug, Clone, PartialEq)]
487pub struct OrderBy {
488    /// `ORDER BY 1` is an ORDINAL into the select list, not the number 1.
489    /// psql's `\dt` ends with `ORDER BY 1,2`, so reading it as a constant
490    /// would silently produce an unordered listing.
491    pub ordinal: Option<usize>,
492    pub expr: Option<Expr>,
493    pub dir: Dir,
494    /// Postgres defaults NULLS LAST for ASC and NULLS FIRST for DESC.
495    pub nulls_first: bool,
496}
497
498#[derive(Debug, Clone, PartialEq)]
499pub struct Select {
500    pub distinct: bool,
501    pub items: Vec<SelectItem>,
502    pub from: Option<TableRef>,
503    pub joins: Vec<Join>,
504    pub where_: Option<Expr>,
505    /// `GROUP BY` keys. Empty means "one group of everything" when the select
506    /// list aggregates, and no grouping at all when it does not.
507    pub group_by: Vec<Expr>,
508    /// `HAVING` — a predicate over each GROUP, evaluated after its aggregates
509    /// are reduced. Distinct from `WHERE`, which filters rows before grouping.
510    pub having: Option<Expr>,
511    /// `ORDER BY` / `LIMIT` / `OFFSET`. On a compound query (`set_ops`
512    /// non-empty) these apply to the COMBINED result, as SQL says, and every
513    /// arm carries none of its own.
514    pub order_by: Vec<OrderBy>,
515    pub limit: Option<usize>,
516    pub offset: Option<usize>,
517    /// The further arms of a `UNION` / `INTERSECT` / `EXCEPT`. Empty for an
518    /// ordinary SELECT. This SELECT's own clauses are the FIRST arm.
519    pub set_ops: Vec<SetArm>,
520}
521
522impl Select {
523    /// Every base relation this query reads, at any depth: the FROM list,
524    /// the joins, derived tables, and every subquery inside an expression or
525    /// a further set-operation arm.
526    ///
527    /// The caller that routes a statement to this engine decides by relation
528    /// name, so a name buried three levels down inside `\dd`'s derived table
529    /// has to surface here or the statement is routed to a path that cannot
530    /// parse it — and reports a confusing error from that path.
531    pub fn base_relations(&self) -> Vec<String> {
532        let mut out = vec![];
533        self.collect_relations(&mut out);
534        out
535    }
536
537    fn collect_relations(&self, out: &mut Vec<String>) {
538        fn table(t: &TableRef, out: &mut Vec<String>) {
539            if let Some(sub) = &t.sub {
540                sub.collect_relations(out);
541            } else if let Some(args) = &t.args {
542                for a in args {
543                    expr(a, out);
544                }
545            } else {
546                out.push(t.name.clone());
547            }
548        }
549        fn expr(e: &Expr, out: &mut Vec<String>) {
550            match e {
551                Expr::Subquery(q) | Expr::ArrayQuery(q) => q.collect_relations(out),
552                Expr::Exists { query, .. } => query.collect_relations(out),
553                Expr::InSubquery { expr: x, query, .. } => {
554                    expr(x, out);
555                    query.collect_relations(out);
556                }
557                Expr::Quantified { left, right, .. } => {
558                    expr(left, out);
559                    expr(right, out);
560                }
561                Expr::Index { expr: x, index } => {
562                    expr(x, out);
563                    expr(index, out);
564                }
565                Expr::ArrayLit(items) | Expr::InList { list: items, .. } => {
566                    if let Expr::InList { expr: x, .. } = e {
567                        expr(x, out);
568                    }
569                    for i in items {
570                        expr(i, out);
571                    }
572                }
573                Expr::Func { args, .. } => {
574                    for a in args {
575                        expr(a, out);
576                    }
577                }
578                Expr::Agg { args, order_by, .. } => {
579                    for a in args {
580                        expr(a, out);
581                    }
582                    for ob in order_by {
583                        if let Some(e) = &ob.expr {
584                            expr(e, out);
585                        }
586                    }
587                }
588                Expr::Case { operand, whens, else_ } => {
589                    if let Some(o) = operand {
590                        expr(o, out);
591                    }
592                    for (w, t) in whens {
593                        expr(w, out);
594                        expr(t, out);
595                    }
596                    if let Some(x) = else_ {
597                        expr(x, out);
598                    }
599                }
600                Expr::Binary { left, right, .. } => {
601                    expr(left, out);
602                    expr(right, out);
603                }
604                Expr::Unary { expr: x, .. } | Expr::Cast { expr: x, .. } | Expr::IsNull { expr: x, .. } => {
605                    expr(x, out)
606                }
607                Expr::Column { .. } | Expr::Literal(_) | Expr::Star | Expr::QualifiedStar(_) => {}
608            }
609        }
610        if let Some(f) = &self.from {
611            table(f, out);
612        }
613        for j in &self.joins {
614            table(&j.table, out);
615            if let Some(on) = &j.on {
616                expr(on, out);
617            }
618        }
619        for item in &self.items {
620            expr(&item.expr, out);
621        }
622        if let Some(w) = &self.where_ {
623            expr(w, out);
624        }
625        for g in &self.group_by {
626            expr(g, out);
627        }
628        if let Some(h) = &self.having {
629            expr(h, out);
630        }
631        for ob in &self.order_by {
632            if let Some(e) = &ob.expr {
633                expr(e, out);
634            }
635        }
636        for arm in &self.set_ops {
637            arm.query.collect_relations(out);
638        }
639    }
640}
641
642// ─────────────────────────────────────────────────────────────────────────────
643// Phase 2b — the parser
644// ─────────────────────────────────────────────────────────────────────────────
645
646/// Binding power for a binary operator. Higher binds tighter.
647///
648/// Written as a table rather than as nested recursive-descent functions so the
649/// precedence is READABLE and auditable in one place — a hand-rolled cascade
650/// is where operator precedence bugs hide, and a precedence bug in a WHERE
651/// clause silently returns the wrong rows.
652fn binding_power(op: &str) -> Option<u8> {
653    Some(match op {
654        "OR" => 1,
655        "AND" => 2,
656        // Comparison and pattern matching sit at the same level, and are
657        // non-associative in Postgres. Left association here is harmless
658        // because chaining them is a type error anyway.
659        "=" | "!=" | "<>" | "<" | "<=" | ">" | ">=" | "~" | "~*" | "!~" | "!~*"
660        | "LIKE" | "ILIKE" | "NOT LIKE" | "NOT ILIKE" => 4,
661        // The escaped forms carry the escape char in the operator string.
662        o if o.starts_with("LIKE ESCAPE ") || o.starts_with("ILIKE ESCAPE ")
663             || o.starts_with("NOT LIKE ESCAPE ") || o.starts_with("NOT ILIKE ESCAPE ") => 4,
664        "||" => 5,
665        "+" | "-" => 6,
666        "*" | "/" | "%" => 7,
667        _ => return None,
668    })
669}
670
671struct Parser {
672    toks: Vec<Tok>,
673    pos: usize,
674}
675
676impl Parser {
677    fn peek(&self) -> &Tok {
678        self.toks.get(self.pos).unwrap_or(&Tok::Eof)
679    }
680    fn peek_at(&self, n: usize) -> &Tok {
681        self.toks.get(self.pos + n).unwrap_or(&Tok::Eof)
682    }
683    fn next(&mut self) -> Tok {
684        let t = self.peek().clone();
685        self.pos += 1;
686        t
687    }
688    fn eat_kw(&mut self, kw: &str) -> bool {
689        if self.peek().is_kw(kw) {
690            self.pos += 1;
691            true
692        } else {
693            false
694        }
695    }
696    fn expect_kw(&mut self, kw: &str) -> Result<()> {
697        if self.eat_kw(kw) {
698            Ok(())
699        } else {
700            bail!("expected {} , got {:?}", kw, self.peek())
701        }
702    }
703    fn eat_punct(&mut self, c: char) -> bool {
704        if matches!(self.peek(), Tok::Punct(p) if *p == c) {
705            self.pos += 1;
706            true
707        } else {
708            false
709        }
710    }
711    fn expect_punct(&mut self, c: char) -> Result<()> {
712        if self.eat_punct(c) {
713            Ok(())
714        } else {
715            bail!("expected {:?}, got {:?}", c, self.peek())
716        }
717    }
718    fn eat_op(&mut self, op: &str) -> bool {
719        if matches!(self.peek(), Tok::Op(o) if o == op) {
720            self.pos += 1;
721            true
722        } else {
723            false
724        }
725    }
726
727    // ── expressions ─────────────────────────────────────────────────────────
728
729    fn parse_expr(&mut self) -> Result<Expr> {
730        self.parse_bin(0)
731    }
732
733    /// Precedence climbing. One loop, one table, no cascade of near-identical
734    /// functions to keep in sync.
735    fn parse_bin(&mut self, min_bp: u8) -> Result<Expr> {
736        let mut left = self.parse_unary()?;
737
738        loop {
739            // A word operator (AND / OR / LIKE / NOT LIKE) and a symbol
740            // operator are both binary here; normalise to one string.
741            // `OPERATOR(pg_catalog.~)` — Postgres's explicit operator
742            // qualification, which psql generates throughout `\d`. It names
743            // exactly the operator it wraps, so the schema is dropped and the
744            // symbol is used directly.
745            if self.peek().is_kw("OPERATOR") && matches!(self.peek_at(1), Tok::Punct('(')) {
746                let save = self.pos;
747                self.pos += 2;
748                // Skip any `schema.` qualification before the symbol.
749                let mut sym = None;
750                while sym.is_none() {
751                    match self.next() {
752                        Tok::Op(o) => sym = Some(o),
753                        Tok::Word { .. } | Tok::Punct('.') => continue,
754                        _ => break,
755                    }
756                }
757                match sym {
758                    Some(o) if binding_power(&o).is_some() && self.eat_punct(')') => {
759                        let bp = binding_power(&o).unwrap();
760                        if bp < min_bp {
761                            self.pos = save;
762                            break;
763                        }
764                        let right = self.parse_bin(bp + 1)?;
765                        left = Expr::Binary {
766                            op: o,
767                            left: Box::new(left),
768                            right: Box::new(right),
769                        };
770                        continue;
771                    }
772                    // Not an operator we know: rewind so the caller reports
773                    // the real position rather than a half-consumed clause.
774                    _ => {
775                        self.pos = save;
776                        break;
777                    }
778                }
779            }
780
781            let (op, width) = match self.peek() {
782                Tok::Op(o) if binding_power(o).is_some() => (o.clone(), 1usize),
783                Tok::Word { upper, .. } if upper == "AND" || upper == "OR" => (upper.clone(), 1),
784                Tok::Word { upper, .. } if upper == "LIKE" || upper == "ILIKE" => (upper.clone(), 1),
785                // `ESCAPE` terminates a LIKE rather than continuing the
786                // expression, and is consumed by the LIKE arm below.
787                Tok::Word { upper, .. } if upper == "ESCAPE" => break,
788                Tok::Word { upper, .. } if upper == "NOT" => {
789                    // `NOT LIKE` / `NOT ILIKE` / `NOT IN` / `NOT BETWEEN`.
790                    match self.peek_at(1) {
791                        Tok::Word { upper: u2, .. } if u2 == "LIKE" || u2 == "ILIKE" => {
792                            (format!("NOT {}", u2), 2)
793                        }
794                        _ => break,
795                    }
796                }
797                _ => break,
798            };
799
800            let bp = match binding_power(&op) {
801                Some(bp) if bp >= min_bp => bp,
802                _ => break,
803            };
804            self.pos += width;
805
806            // `x op ANY (...)` / `SOME` / `ALL` — a quantified comparison.
807            // psql's `\dp` writes `oid = ANY (polroles)`, `\dX` writes
808            // `'d' = any(es.stxkind)`. The right side is either an array value
809            // or a subquery, and the subquery form is read as ARRAY(SELECT)
810            // so one evaluator serves both.
811            let quant = match self.peek() {
812                Tok::Word { upper, .. }
813                    if matches!(upper.as_str(), "ANY" | "SOME" | "ALL")
814                        && matches!(self.peek_at(1), Tok::Punct('(')) =>
815                {
816                    Some(upper == "ALL")
817                }
818                _ => None,
819            };
820            if let Some(all) = quant {
821                self.pos += 2; // the word and the `(`
822                let right = if self.peek().is_kw("SELECT") {
823                    Expr::ArrayQuery(Box::new(self.parse_query()?))
824                } else {
825                    self.parse_expr()?
826                };
827                self.expect_punct(')')?;
828                left = Expr::Quantified { op, left: Box::new(left), all, right: Box::new(right) };
829                continue;
830            }
831
832            // Left-associative: the right side binds tighter than this level.
833            let right = self.parse_bin(bp + 1)?;
834
835            // `LIKE <pat> ESCAPE '<c>'` — the escape char rides ON the operator
836            // string rather than widening `Expr::Binary`. That keeps every
837            // existing match arm, pushdown check and pretty-printer working on
838            // a two-operand binary, and `eval` splits it back apart. Widening
839            // the node would have meant touching every site that matches
840            // Binary, which is where a missed arm silently drops the clause.
841            let op = if matches!(op.as_str(), "LIKE" | "ILIKE" | "NOT LIKE" | "NOT ILIKE")
842                && self.peek().is_kw("ESCAPE")
843            {
844                self.next();
845                match self.next() {
846                    Tok::Str(e) => {
847                        let mut ch = e.chars();
848                        match (ch.next(), ch.next()) {
849                            // Postgres requires exactly one character.
850                            (Some(c), None) => format!("{} ESCAPE {}", op, c),
851                            _ => bail!(
852                                "ESCAPE takes a single-character string, got {:?}", e),
853                        }
854                    }
855                    other => bail!("ESCAPE takes a string literal, got {:?}", other),
856                }
857            } else {
858                op
859            };
860
861            left = Expr::Binary { op, left: Box::new(left), right: Box::new(right) };
862        }
863
864        Ok(left)
865    }
866
867    fn parse_postfix(&mut self, mut e: Expr) -> Result<Expr> {
868        loop {
869            // `arr[i]` — a subscript. psql's publication query writes
870            // `prattrs[s]`.
871            if matches!(self.peek(), Tok::Punct('[')) {
872                self.pos += 1;
873                let index = self.parse_expr()?;
874                self.expect_punct(']')?;
875                e = Expr::Index { expr: Box::new(e), index: Box::new(index) };
876                continue;
877            }
878
879            // IS [NOT] NULL
880            if self.peek().is_kw("IS") {
881                self.pos += 1;
882                let negated = self.eat_kw("NOT");
883                // `IS [NOT] DISTINCT FROM` — the null-safe comparison, which
884                // psql's `\dconfig` uses. Never UNKNOWN: two NULLs are not
885                // distinct, a NULL and a value are.
886                if self.eat_kw("DISTINCT") {
887                    self.expect_kw("FROM")?;
888                    // Binds like a comparison: the operand is parsed above AND.
889                    let rhs = self.parse_bin(5)?;
890                    e = Expr::Binary {
891                        op: if negated { "IS NOT DISTINCT FROM".into() } else { "IS DISTINCT FROM".into() },
892                        left: Box::new(e),
893                        right: Box::new(rhs),
894                    };
895                    continue;
896                }
897                if !self.eat_kw("NULL") {
898                    // `IS TRUE` / `IS FALSE` are the other legal spellings.
899                    if self.eat_kw("TRUE") {
900                        e = Expr::Binary {
901                            op: "=".into(),
902                            left: Box::new(e),
903                            right: Box::new(Expr::Literal(Value::Bool(!negated))),
904                        };
905                        continue;
906                    }
907                    if self.eat_kw("FALSE") {
908                        e = Expr::Binary {
909                            op: "=".into(),
910                            left: Box::new(e),
911                            right: Box::new(Expr::Literal(Value::Bool(negated))),
912                        };
913                        continue;
914                    }
915                    bail!("expected NULL, TRUE or FALSE after IS, got {:?}", self.peek());
916                }
917                e = Expr::IsNull { expr: Box::new(e), negated };
918                continue;
919            }
920
921            // [NOT] IN (...)
922            let negated_in = if self.peek().is_kw("NOT") && self.peek_at(1).is_kw("IN") {
923                self.pos += 2;
924                true
925            } else if self.peek().is_kw("IN") {
926                self.pos += 1;
927                false
928            } else {
929                // [NOT] BETWEEN a AND b
930                let negated_between =
931                    if self.peek().is_kw("NOT") && self.peek_at(1).is_kw("BETWEEN") {
932                        self.pos += 2;
933                        true
934                    } else if self.peek().is_kw("BETWEEN") {
935                        self.pos += 1;
936                        false
937                    } else {
938                        break;
939                    };
940                // BETWEEN's bounds bind tighter than AND, so the bounds are
941                // parsed at a level above AND — otherwise `BETWEEN a AND b`
942                // swallows the AND as a boolean operator.
943                let low = self.parse_bin(3)?;
944                self.expect_kw("AND")?;
945                let high = self.parse_bin(3)?;
946                let ge = Expr::Binary {
947                    op: ">=".into(),
948                    left: Box::new(e.clone()),
949                    right: Box::new(low),
950                };
951                let le = Expr::Binary {
952                    op: "<=".into(),
953                    left: Box::new(e),
954                    right: Box::new(high),
955                };
956                let both = Expr::Binary {
957                    op: "AND".into(),
958                    left: Box::new(ge),
959                    right: Box::new(le),
960                };
961                e = if negated_between {
962                    Expr::Unary { op: "NOT".into(), expr: Box::new(both) }
963                } else {
964                    both
965                };
966                continue;
967            };
968
969            self.expect_punct('(')?;
970            // `x IN (SELECT ...)` — the list is a subquery's first column.
971            if self.peek().is_kw("SELECT") {
972                let query = Box::new(self.parse_query()?);
973                self.expect_punct(')')?;
974                e = Expr::InSubquery { expr: Box::new(e), query, negated: negated_in };
975                continue;
976            }
977            let mut list = vec![];
978            if !self.eat_punct(')') {
979                loop {
980                    list.push(self.parse_expr()?);
981                    if self.eat_punct(',') {
982                        continue;
983                    }
984                    self.expect_punct(')')?;
985                    break;
986                }
987            }
988            e = Expr::InList { expr: Box::new(e), list, negated: negated_in };
989        }
990        Ok(e)
991    }
992
993    fn parse_unary(&mut self) -> Result<Expr> {
994        if self.peek().is_kw("NOT") {
995            self.pos += 1;
996            // NOT binds looser than comparison, so its operand is parsed at
997            // the comparison level: `NOT a = b` is `NOT (a = b)`.
998            let e = self.parse_bin(3)?;
999            return Ok(Expr::Unary { op: "NOT".into(), expr: Box::new(e) });
1000        }
1001        if self.eat_op("-") {
1002            let e = self.parse_unary()?;
1003            return Ok(Expr::Unary { op: "-".into(), expr: Box::new(e) });
1004        }
1005        if self.eat_op("+") {
1006            return self.parse_unary();
1007        }
1008        let atom = self.parse_atom()?;
1009        let cast = self.parse_casts(atom)?;
1010        // Postfix forms (`IS NULL`, `IN (...)`, `BETWEEN a AND b`) bind to the
1011        // OPERAND, before any binary operator is considered.
1012        //
1013        // They used to be applied after the binary loop in `parse_bin`, which
1014        // meant that once `IN (...)` was consumed the loop had already exited
1015        // and the rest of the predicate was left unparsed. psql's `\dt` is
1016        // `WHERE c.relkind IN (...) AND n.nspname <> '...' AND ...`, so
1017        // everything after the IN list silently became "trailing tokens" — and
1018        // a WHERE clause that loses its later conjuncts returns TOO MANY rows,
1019        // confidently.
1020        self.parse_postfix(cast)
1021    }
1022
1023    /// `expr::type`, possibly repeated and possibly `type[]`, and `COLLATE`.
1024    fn parse_casts(&mut self, mut e: Expr) -> Result<Expr> {
1025        loop {
1026            // `COLLATE "C"` — psql writes it throughout `\d`. NEDB has one
1027            // collation, so it cannot change the answer; it is consumed rather
1028            // than refused, because refusing a clause that provably has no
1029            // effect would reject a query whose result is already correct.
1030            if self.peek().is_kw("COLLATE") {
1031                self.pos += 1;
1032                match self.next() {
1033                    Tok::Word { .. } | Tok::Quoted(_) => {}
1034                    other => bail!("expected a collation name after COLLATE, got {:?}", other),
1035                }
1036                // A schema-qualified collation: `pg_catalog."C"`.
1037                while self.eat_punct('.') {
1038                    match self.next() {
1039                        Tok::Word { .. } | Tok::Quoted(_) => {}
1040                        other => bail!("expected a name after '.', got {:?}", other),
1041                    }
1042                }
1043                continue;
1044            }
1045            if !self.eat_op("::") {
1046                break;
1047            }
1048            let mut ty = match self.next() {
1049                Tok::Word { raw, .. } => raw,
1050                Tok::Quoted(s) => s,
1051                other => bail!("expected a type name after ::, got {:?}", other),
1052            };
1053            // A schema-qualified type: `pg_catalog.int2`.
1054            while self.eat_punct('.') {
1055                match self.next() {
1056                    Tok::Word { raw, .. } => ty = raw,
1057                    Tok::Quoted(s) => ty = s,
1058                    other => bail!("expected a type name after ., got {:?}", other),
1059                }
1060            }
1061            // An array type: `int2[]`.
1062            while self.eat_punct('[') {
1063                self.expect_punct(']')?;
1064                ty.push_str("[]");
1065            }
1066            e = Expr::Cast { expr: Box::new(e), ty };
1067        }
1068        Ok(e)
1069    }
1070
1071
1072    fn parse_atom(&mut self) -> Result<Expr> {
1073        // ( expr ) — or a SUBQUERY, which is named rather than reported as a
1074        // stray parenthesis.
1075        //
1076        // "expected ')', got SELECT" is a parser internal and tells the reader
1077        // nothing about what to change. `\d` and `\dp` both hinge on
1078        // subqueries, so this is the message somebody will actually read.
1079        if self.eat_punct('(') {
1080            // A scalar subquery. It may carry its own ORDER BY / LIMIT, and
1081            // may itself be a UNION, so it is a full query.
1082            if self.peek().is_kw("SELECT") {
1083                let q = self.parse_query()?;
1084                self.expect_punct(')')?;
1085                return Ok(Expr::Subquery(Box::new(q)));
1086            }
1087            let e = self.parse_expr()?;
1088            self.expect_punct(')')?;
1089            return Ok(e);
1090        }
1091
1092        // `ARRAY(SELECT ...)` and `ARRAY[a, b]`. The first appears throughout
1093        // psql's `\dp`, `\dT+`, `\dD` and `\dy`; it is a subquery wearing a
1094        // function's clothes, and its value is the first column of every row.
1095        if self.peek().is_kw("ARRAY") && matches!(self.peek_at(1), Tok::Punct('(') | Tok::Punct('[')) {
1096            self.pos += 1;
1097            if self.eat_punct('(') {
1098                if !self.peek().is_kw("SELECT") {
1099                    bail!("ARRAY(...) takes a subquery; for a list of values write ARRAY[...]");
1100                }
1101                let q = self.parse_query()?;
1102                self.expect_punct(')')?;
1103                return Ok(Expr::ArrayQuery(Box::new(q)));
1104            }
1105            self.expect_punct('[')?;
1106            let mut items = vec![];
1107            if !self.eat_punct(']') {
1108                loop {
1109                    items.push(self.parse_expr()?);
1110                    if self.eat_punct(',') {
1111                        continue;
1112                    }
1113                    self.expect_punct(']')?;
1114                    break;
1115                }
1116            }
1117            return Ok(Expr::ArrayLit(items));
1118        }
1119
1120        // `EXISTS (SELECT ...)`. `NOT EXISTS` arrives here through
1121        // `parse_unary`'s NOT and is wrapped there, which is correct because
1122        // EXISTS is never NULL and NOT of a boolean is exact.
1123        if self.peek().is_kw("EXISTS") && matches!(self.peek_at(1), Tok::Punct('(')) {
1124            self.pos += 2;
1125            if !self.peek().is_kw("SELECT") {
1126                bail!("EXISTS (...) takes a subquery");
1127            }
1128            let q = self.parse_query()?;
1129            self.expect_punct(')')?;
1130            return Ok(Expr::Exists { query: Box::new(q), negated: false });
1131        }
1132
1133        // `CAST(expr AS type)` — the standard spelling of `expr::type`, which
1134        // psql's `\dT+` and `\dd` both use. Recorded the same way.
1135        if self.peek().is_kw("CAST") && matches!(self.peek_at(1), Tok::Punct('(')) {
1136            self.pos += 2;
1137            let inner = self.parse_expr()?;
1138            self.expect_kw("AS")?;
1139            let mut ty = match self.next() {
1140                Tok::Word { raw, .. } => raw,
1141                Tok::Quoted(s) => s,
1142                other => bail!("expected a type name in CAST, got {:?}", other),
1143            };
1144            while self.eat_punct('.') {
1145                match self.next() {
1146                    Tok::Word { raw, .. } => ty = raw,
1147                    Tok::Quoted(s) => ty = s,
1148                    other => bail!("expected a type name after ., got {:?}", other),
1149                }
1150            }
1151            while self.eat_punct('[') {
1152                self.expect_punct(']')?;
1153                ty.push_str("[]");
1154            }
1155            self.expect_punct(')')?;
1156            return Ok(Expr::Cast { expr: Box::new(inner), ty });
1157        }
1158
1159        // CASE
1160        if self.peek().is_kw("CASE") {
1161            return self.parse_case();
1162        }
1163
1164        match self.next() {
1165            Tok::Num(n) => Ok(Expr::Literal(from_f64(n))),
1166            Tok::Str(s) => Ok(Expr::Literal(Value::String(s))),
1167            Tok::Op(o) if o == "*" => Ok(Expr::Star),
1168            Tok::Quoted(name) => self.parse_name_tail(None, name),
1169            Tok::Word { upper, raw } => match upper.as_str() {
1170                "NULL" => Ok(Expr::Literal(Value::Null)),
1171                "TRUE" => Ok(Expr::Literal(Value::Bool(true))),
1172                "FALSE" => Ok(Expr::Literal(Value::Bool(false))),
1173                // `CURRENT_SCHEMA` and friends are functions spelled without
1174                // parentheses. Treated as zero-argument calls so one evaluator
1175                // handles both spellings.
1176                "CURRENT_SCHEMA" | "CURRENT_DATABASE" | "CURRENT_USER" | "SESSION_USER"
1177                | "CURRENT_CATALOG" | "USER" | "VERSION"
1178                    if !matches!(self.peek(), Tok::Punct('(')) =>
1179                {
1180                    Ok(Expr::Func { name: upper.to_lowercase(), args: vec![] })
1181                }
1182                _ => self.parse_name_tail(None, raw),
1183            },
1184            other => bail!("unexpected {:?} in an expression", other),
1185        }
1186    }
1187
1188    /// After an identifier: `.more`, `(args)`, or nothing.
1189    ///
1190    /// This is where `pg_catalog.pg_get_userbyid(x)` and `n.nspname` and a
1191    /// bare `relname` all get told apart, and the rule is positional: the LAST
1192    /// dotted part before a `(` is the function name; before anything else it
1193    /// is the column, and the part before it is the qualifier.
1194    fn parse_name_tail(&mut self, _schema: Option<String>, first: String) -> Result<Expr> {
1195        let mut parts = vec![first];
1196        while self.eat_punct('.') {
1197            // `c.*`
1198            if self.eat_op("*") {
1199                return Ok(Expr::QualifiedStar(parts.pop().unwrap_or_default()));
1200            }
1201            match self.next() {
1202                Tok::Word { raw, .. } => parts.push(raw),
1203                Tok::Quoted(s) => parts.push(s),
1204                other => bail!("expected a name after '.', got {:?}", other),
1205            }
1206        }
1207
1208        // A call: the last part is the function, any earlier parts are its
1209        // schema and are dropped — `pg_catalog.pg_get_userbyid` is the same
1210        // function as `pg_get_userbyid`.
1211        if matches!(self.peek(), Tok::Punct('(')) {
1212            self.pos += 1;
1213            let name = parts.pop().unwrap_or_default().to_lowercase();
1214            let agg = is_aggregate(&name);
1215            // `count(DISTINCT x)` — only legal on an aggregate.
1216            let distinct = agg && self.eat_kw("DISTINCT");
1217            let mut args = vec![];
1218            let mut order_by = vec![];
1219            if !self.eat_punct(')') {
1220                loop {
1221                    // `count(*)`
1222                    if self.eat_op("*") {
1223                        args.push(Expr::Star);
1224                    } else {
1225                        args.push(self.parse_expr()?);
1226                    }
1227                    if self.eat_punct(',') {
1228                        continue;
1229                    }
1230                    // `array_agg(x ORDER BY y DESC)` — the aggregate's own
1231                    // ordering, INSIDE the call. Without it SQLAlchemy's
1232                    // primary-key reflection does not parse.
1233                    if agg && self.peek().is_kw("ORDER") {
1234                        self.pos += 1;
1235                        self.expect_kw("BY")?;
1236                        order_by = self.parse_sort_list()?;
1237                    }
1238                    self.expect_punct(')')?;
1239                    break;
1240                }
1241            }
1242            if agg {
1243                return Ok(Expr::Agg { name, args, order_by, distinct });
1244            }
1245            return Ok(Expr::Func { name, args });
1246        }
1247
1248        let name = parts.pop().unwrap_or_default();
1249        // Only the IMMEDIATE qualifier matters: in `public.orders.id` the
1250        // binding is `orders`, and the schema is not part of how a column is
1251        // addressed.
1252        let qual = parts.pop();
1253        Ok(Expr::Column { qual, name })
1254    }
1255
1256    fn parse_case(&mut self) -> Result<Expr> {
1257        self.expect_kw("CASE")?;
1258        // A simple CASE has an operand; a searched CASE goes straight to WHEN.
1259        let operand = if self.peek().is_kw("WHEN") {
1260            None
1261        } else {
1262            Some(Box::new(self.parse_expr()?))
1263        };
1264        let mut whens = vec![];
1265        while self.eat_kw("WHEN") {
1266            let cond = self.parse_expr()?;
1267            self.expect_kw("THEN")?;
1268            let then = self.parse_expr()?;
1269            whens.push((cond, then));
1270        }
1271        if whens.is_empty() {
1272            bail!("CASE needs at least one WHEN branch");
1273        }
1274        let else_ = if self.eat_kw("ELSE") {
1275            Some(Box::new(self.parse_expr()?))
1276        } else {
1277            None
1278        };
1279        self.expect_kw("END")?;
1280        Ok(Expr::Case { operand, whens, else_ })
1281    }
1282
1283    // ── the statement ───────────────────────────────────────────────────────
1284
1285    fn parse_table_ref(&mut self) -> Result<TableRef> {
1286        let lateral = self.eat_kw("LATERAL");
1287        // `( SELECT ... ) AS t` — a derived table. psql's `\dd` is one.
1288        if self.eat_punct('(') {
1289            if !self.peek().is_kw("SELECT") {
1290                bail!("expected a subquery after '(' in FROM, got {:?}", self.peek());
1291            }
1292            let sub = self.parse_query()?;
1293            self.expect_punct(')')?;
1294            let (alias, col_aliases) = self.parse_table_alias()?;
1295            if alias.is_none() {
1296                bail!("a subquery in FROM must have an alias");
1297            }
1298            return Ok(TableRef {
1299                name: "(subquery)".into(),
1300                alias,
1301                sub: Some(Box::new(sub)),
1302                args: None,
1303                col_aliases,
1304                lateral,
1305                // A subquery carries no table-level qualifiers: it is already a
1306                // relation, and anything temporal or causal was said inside it.
1307                trace: None,
1308                trace_reverse: false,
1309                traverse: None,
1310                as_of: None,
1311                valid_as_of: None,
1312                search: None,
1313            });
1314        }
1315        if lateral {
1316            bail!("LATERAL applies to a subquery in FROM; write LATERAL (SELECT ...)");
1317        }
1318
1319        let mut parts = vec![match self.next() {
1320            Tok::Word { raw, .. } => raw,
1321            Tok::Quoted(s) => s,
1322            other => bail!("expected a table name, got {:?}", other),
1323        }];
1324        while self.eat_punct('.') {
1325            match self.next() {
1326                Tok::Word { raw, .. } => parts.push(raw),
1327                Tok::Quoted(s) => parts.push(s),
1328                other => bail!("expected a name after '.', got {:?}", other),
1329            }
1330        }
1331        let name = parts.join(".");
1332
1333        // `generate_series(0, n) s` / `unnest(arr) AS t(x)` — a table
1334        // function. The schema qualification is dropped, as for scalar calls.
1335        if self.eat_punct('(') {
1336            let mut args = vec![];
1337            if !self.eat_punct(')') {
1338                loop {
1339                    args.push(self.parse_expr()?);
1340                    if self.eat_punct(',') {
1341                        continue;
1342                    }
1343                    self.expect_punct(')')?;
1344                    break;
1345                }
1346            }
1347            let fname = name.rsplit('.').next().unwrap_or(&name).to_lowercase();
1348            let (alias, col_aliases) = self.parse_table_alias()?;
1349            return Ok(TableRef { name: fname, alias, sub: None, args: Some(args), col_aliases, lateral: false, as_of: None, valid_as_of: None, search: None, trace: None, trace_reverse: false, traverse: None });
1350        }
1351
1352        // `AS OF SYSTEM TIME <seq>` is read BEFORE the alias, because `AS` is
1353        // the first token of both this and `AS <alias>`. The word after `AS`
1354        // decides which one it is, and `parse_table_alias` would otherwise
1355        // consume `OF` as the alias and leave `SYSTEM TIME 42` in the stream.
1356        let as_of = if self.peek().is_kw("AS") && self.peek_at(1).is_kw("OF") {
1357            self.next();
1358            self.next();
1359            self.expect_kw("SYSTEM")?;
1360            self.expect_kw("TIME")?;
1361            match self.next() {
1362                Tok::Num(n) if n >= 0.0 && n.fract() == 0.0 => Some(n as u64),
1363                other => bail!(
1364                    "AS OF SYSTEM TIME takes a NEDB sequence number here, not a timestamp \
1365                     (got {:?}). NEDB's history is sequence-addressed and never \
1366                     garbage-collected, so a seq is exact where a wall-clock time would be \
1367                     approximate",
1368                    other
1369                ),
1370            }
1371        } else {
1372            None
1373        };
1374        // ── NQL's own verbs, as table-level qualifiers ──────────────────────
1375        //
1376        // This is what "NQL folded into neSQL" means concretely: the verbs NQL
1377        // has and SQL has no spelling for become qualifiers on the relation,
1378        // so a SQL statement can SAY them and the SQL evaluator can compose
1379        // joins, subqueries and set operations AROUND them. The NQL engine
1380        // still executes them — the resolver renders them straight back into
1381        // the NQL it asks for — so there is one implementation, not two.
1382        //
1383        // Both are UNRESERVED, and deliberately: `VALID` only starts a clause
1384        // when `AS OF` follows, and `SEARCH` only when a string literal
1385        // follows. So a collection aliased `search`, or a column named
1386        // `valid`, keeps working — the same discipline the PostgreSQL grammar
1387        // uses with `unreserved_keyword`, and the reason adding a verb does
1388        // not break somebody's existing data.
1389        let valid_as_of = if self.peek().is_kw("VALID")
1390            && self.peek_at(1).is_kw("AS")
1391            && self.peek_at(2).is_kw("OF")
1392        {
1393            self.next();
1394            self.next();
1395            self.next();
1396            match self.next() {
1397                Tok::Str(s) => Some(s),
1398                other => bail!(
1399                    "VALID AS OF takes a date string here (got {:?}). System time is a \
1400                     sequence and application-time validity is a date — they are different \
1401                     questions, so they take different arguments",
1402                    other
1403                ),
1404            }
1405        } else {
1406            None
1407        };
1408
1409        let search = if self.peek().is_kw("SEARCH") && matches!(self.peek_at(1), Tok::Str(_)) {
1410            self.next();
1411            match self.next() {
1412                Tok::Str(s) => Some(s),
1413                // Unreachable given the lookahead above, but an unreachable
1414                // branch that bails is cheaper than one that panics.
1415                other => bail!("SEARCH takes a string here, got {:?}", other),
1416            }
1417        } else {
1418            None
1419        };
1420
1421        // `TRACE <edge> [REVERSE]` and `TRAVERSE <rel>`, on the same terms as
1422        // the three above. The lookahead requires an IDENTIFIER, which is what
1423        // keeps both unreserved: `FROM orders trace` is still a relation
1424        // aliased `trace`, because no identifier follows it.
1425        // A name, for the purposes of the lookahead: a bare word that does not
1426        // start the next clause, or a quoted identifier. Keywords are `Word`s
1427        // too, so testing for "a word follows" is not enough — `FROM orders
1428        // TRACE WHERE x = 1` would take `WHERE` as the edge type. This is the
1429        // same test `parse_table_alias` makes, for the same reason.
1430        fn names_a_thing(t: &Tok) -> bool {
1431            match t {
1432                Tok::Word { upper, .. } => !is_clause_keyword(upper),
1433                Tok::Quoted(_) => true,
1434                _ => false,
1435            }
1436        }
1437
1438        let (trace, trace_reverse) = if self.peek().is_kw("TRACE")
1439            && names_a_thing(self.peek_at(1))
1440        {
1441            self.next();
1442            let edge = match self.next() {
1443                Tok::Word { raw, .. } => raw,
1444                Tok::Quoted(s) => s,
1445                other => bail!("TRACE takes an edge type here, got {:?}", other),
1446            };
1447            let rev = if self.peek().is_kw("REVERSE") {
1448                self.next();
1449                true
1450            } else {
1451                false
1452            };
1453            (Some(edge), rev)
1454        } else {
1455            (None, false)
1456        };
1457
1458        let traverse = if self.peek().is_kw("TRAVERSE")
1459            && names_a_thing(self.peek_at(1))
1460        {
1461            self.next();
1462            match self.next() {
1463                Tok::Word { raw, .. } => Some(raw),
1464                Tok::Quoted(s) => Some(s),
1465                other => bail!("TRAVERSE takes a relation name here, got {:?}", other),
1466            }
1467        } else {
1468            None
1469        };
1470
1471        let (alias, col_aliases) = self.parse_table_alias()?;
1472        Ok(TableRef { name, alias, sub: None, args: None, col_aliases, lateral: false, as_of, valid_as_of, search, trace, trace_reverse, traverse })
1473    }
1474
1475    /// `AS alias`, or a bare alias, optionally followed by `(col, col)`.
1476    ///
1477    /// A bare alias must not swallow a keyword that starts the next clause,
1478    /// or `FROM t WHERE x` reads `t` aliased as `WHERE`.
1479    fn parse_table_alias(&mut self) -> Result<(Option<String>, Vec<String>)> {
1480        let alias = if self.eat_kw("AS") {
1481            match self.next() {
1482                Tok::Word { raw, .. } => Some(raw),
1483                Tok::Quoted(s) => Some(s),
1484                other => bail!("expected an alias after AS, got {:?}", other),
1485            }
1486        } else {
1487            match self.peek().clone() {
1488                Tok::Word { upper, raw } if !is_clause_keyword(&upper) => {
1489                    self.pos += 1;
1490                    Some(raw)
1491                }
1492                Tok::Quoted(s) => {
1493                    self.pos += 1;
1494                    Some(s)
1495                }
1496                _ => None,
1497            }
1498        };
1499        let mut col_aliases = vec![];
1500        if alias.is_some() && self.eat_punct('(') {
1501            loop {
1502                match self.next() {
1503                    Tok::Word { raw, .. } => col_aliases.push(raw),
1504                    Tok::Quoted(s) => col_aliases.push(s),
1505                    other => bail!("expected a column alias, got {:?}", other),
1506                }
1507                if self.eat_punct(',') {
1508                    continue;
1509                }
1510                self.expect_punct(')')?;
1511                break;
1512            }
1513        }
1514        Ok((alias, col_aliases))
1515    }
1516
1517    /// A full query: one or more SELECT bodies joined by set operations, then
1518    /// the ORDER BY / LIMIT / OFFSET that apply to the whole.
1519    ///
1520    /// The tail is parsed HERE and not in the body, because after `a UNION b
1521    /// ORDER BY 1` the ORDER BY sorts the union — attaching it to `b` would
1522    /// sort one arm and leave the result unordered while reporting success.
1523    fn parse_query(&mut self) -> Result<Select> {
1524        let mut first = self.parse_select_body()?;
1525        loop {
1526            let op = if self.eat_kw("UNION") {
1527                SetOp::Union
1528            } else if self.eat_kw("INTERSECT") {
1529                SetOp::Intersect
1530            } else if self.eat_kw("EXCEPT") {
1531                SetOp::Except
1532            } else {
1533                break;
1534            };
1535            let all = self.eat_kw("ALL");
1536            if !all {
1537                let _ = self.eat_kw("DISTINCT");
1538            }
1539            // A parenthesised arm: `UNION (SELECT ...)`.
1540            let query = if self.eat_punct('(') {
1541                let q = self.parse_query()?;
1542                self.expect_punct(')')?;
1543                q
1544            } else {
1545                self.parse_select_body()?
1546            };
1547            first.set_ops.push(SetArm { op, all, query });
1548        }
1549        self.parse_query_tail(&mut first)?;
1550        Ok(first)
1551    }
1552
1553    /// A comma-separated sort list — shared by the query tail and by an
1554    /// aggregate's own `ORDER BY`, so the two cannot disagree about how a
1555    /// sort key, a direction or a NULLS placement is spelled.
1556    fn parse_sort_list(&mut self) -> Result<Vec<OrderBy>> {
1557        let mut out = vec![];
1558        loop {
1559            // `ORDER BY 1` is an ORDINAL into the select list, not the
1560            // literal 1. Reading it as a constant sorts every row equally
1561            // and silently yields an unordered result.
1562            let (ordinal, expr) = match self.peek().clone() {
1563                Tok::Num(n)
1564                    if n.fract() == 0.0
1565                        && n >= 1.0
1566                        && !matches!(self.peek_at(1), Tok::Op(_)) =>
1567                {
1568                    self.pos += 1;
1569                    (Some(n as usize), None)
1570                }
1571                _ => (None, Some(self.parse_expr()?)),
1572            };
1573            let dir = if self.eat_kw("DESC") {
1574                Dir::Desc
1575            } else {
1576                let _ = self.eat_kw("ASC");
1577                Dir::Asc
1578            };
1579            // Postgres defaults NULLS LAST for ASC, NULLS FIRST for DESC.
1580            let mut nulls_first = matches!(dir, Dir::Desc);
1581            if self.eat_kw("NULLS") {
1582                if self.eat_kw("FIRST") {
1583                    nulls_first = true;
1584                } else if self.eat_kw("LAST") {
1585                    nulls_first = false;
1586                } else {
1587                    bail!("expected FIRST or LAST after NULLS, got {:?}", self.peek());
1588                }
1589            }
1590            out.push(OrderBy { ordinal, expr, dir, nulls_first });
1591            if self.eat_punct(',') {
1592                continue;
1593            }
1594            break;
1595        }
1596        Ok(out)
1597    }
1598
1599    fn parse_query_tail(&mut self, sel: &mut Select) -> Result<()> {
1600        let mut order_by = vec![];
1601        if self.eat_kw("ORDER") {
1602            self.expect_kw("BY")?;
1603            order_by = self.parse_sort_list()?;
1604        }
1605
1606        let mut limit = None;
1607        let mut offset = None;
1608        // Either order, and either may appear alone.
1609        loop {
1610            if self.eat_kw("LIMIT") {
1611                if self.eat_kw("ALL") {
1612                    limit = None;
1613                } else {
1614                    limit = Some(self.parse_count("LIMIT")?);
1615                }
1616                continue;
1617            }
1618            if self.eat_kw("OFFSET") {
1619                offset = Some(self.parse_count("OFFSET")?);
1620                let _ = self.eat_kw("ROW") || self.eat_kw("ROWS");
1621                continue;
1622            }
1623            break;
1624        }
1625        sel.order_by = order_by;
1626        sel.limit = limit;
1627        sel.offset = offset;
1628        Ok(())
1629    }
1630
1631    /// One `SELECT ... FROM ... WHERE ...` body, without the query tail.
1632    fn parse_select_body(&mut self) -> Result<Select> {
1633        self.expect_kw("SELECT")?;
1634        let distinct = self.eat_kw("DISTINCT");
1635        if distinct && self.peek().is_kw("ON") {
1636            bail!("DISTINCT ON is not supported");
1637        }
1638        let _ = self.eat_kw("ALL");
1639
1640        let mut items = vec![];
1641        loop {
1642            let expr = self.parse_expr()?;
1643            // `AS "Name"`, or a bare alias that is not a clause keyword.
1644            let alias = if self.eat_kw("AS") {
1645                match self.next() {
1646                    Tok::Word { raw, .. } => Some(raw),
1647                    Tok::Quoted(s) => Some(s),
1648                    other => bail!("expected an alias after AS, got {:?}", other),
1649                }
1650            } else {
1651                match self.peek().clone() {
1652                    Tok::Word { upper, raw } if !is_clause_keyword(&upper) => {
1653                        self.pos += 1;
1654                        Some(raw)
1655                    }
1656                    Tok::Quoted(s) => {
1657                        self.pos += 1;
1658                        Some(s)
1659                    }
1660                    _ => None,
1661                }
1662            };
1663            items.push(SelectItem { expr, alias });
1664            if self.eat_punct(',') {
1665                continue;
1666            }
1667            break;
1668        }
1669
1670        let mut from = None;
1671        let mut joins = vec![];
1672        if self.eat_kw("FROM") {
1673            from = Some(self.parse_table_ref()?);
1674            loop {
1675                // A comma-separated FROM item is an implicit CROSS JOIN, and
1676                // it may INTERLEAVE with explicit joins: psql's `\dF+` writes
1677                // `FROM c LEFT JOIN n ON ..., p LEFT JOIN np ON ...`. Reading
1678                // the commas first and the joins second would parse that as
1679                // trailing tokens.
1680                if self.eat_punct(',') {
1681                    let table = self.parse_table_ref()?;
1682                    joins.push(Join { kind: JoinKind::Cross, table, on: None });
1683                    continue;
1684                }
1685                let kind = if self.peek().is_kw("JOIN") {
1686                    self.pos += 1;
1687                    JoinKind::Inner
1688                } else if self.peek().is_kw("INNER") && self.peek_at(1).is_kw("JOIN") {
1689                    self.pos += 2;
1690                    JoinKind::Inner
1691                } else if self.peek().is_kw("CROSS") && self.peek_at(1).is_kw("JOIN") {
1692                    self.pos += 2;
1693                    JoinKind::Cross
1694                } else if self.peek().is_kw("LEFT") {
1695                    self.pos += 1;
1696                    let _ = self.eat_kw("OUTER");
1697                    self.expect_kw("JOIN")?;
1698                    JoinKind::Left
1699                } else if self.peek().is_kw("RIGHT") {
1700                    self.pos += 1;
1701                    let _ = self.eat_kw("OUTER");
1702                    self.expect_kw("JOIN")?;
1703                    JoinKind::Right
1704                } else if self.peek().is_kw("FULL") {
1705                    self.pos += 1;
1706                    let _ = self.eat_kw("OUTER");
1707                    self.expect_kw("JOIN")?;
1708                    JoinKind::Full
1709                } else {
1710                    break;
1711                };
1712                let table = self.parse_table_ref()?;
1713                let on = if self.eat_kw("ON") {
1714                    Some(self.parse_expr()?)
1715                } else if self.peek().is_kw("USING") {
1716                    bail!("JOIN ... USING is not supported — write ON a.col = b.col");
1717                } else {
1718                    None
1719                };
1720                if on.is_none() && !matches!(kind, JoinKind::Cross) {
1721                    bail!("a {:?} JOIN needs an ON clause", kind);
1722                }
1723                joins.push(Join { kind, table, on });
1724            }
1725        }
1726
1727        let where_ = if self.eat_kw("WHERE") {
1728            Some(self.parse_expr()?)
1729        } else {
1730            None
1731        };
1732
1733        let mut group_by = vec![];
1734        if self.eat_kw("GROUP") {
1735            self.expect_kw("BY")?;
1736            if self.eat_kw("ALL") || self.eat_kw("DISTINCT") {
1737                bail!("GROUP BY ALL / DISTINCT is not supported — list the keys");
1738            }
1739            loop {
1740                if self.peek().is_kw("ROLLUP")
1741                    || self.peek().is_kw("CUBE")
1742                    || self.peek().is_kw("GROUPING")
1743                {
1744                    bail!("GROUP BY ROLLUP / CUBE / GROUPING SETS is not supported");
1745                }
1746                group_by.push(self.parse_expr()?);
1747                if self.eat_punct(',') {
1748                    continue;
1749                }
1750                break;
1751            }
1752        }
1753
1754        let having = if self.eat_kw("HAVING") {
1755            Some(self.parse_expr()?)
1756        } else {
1757            None
1758        };
1759        if having.is_some() && group_by.is_empty() && !items.iter().any(|i| has_aggregate(&i.expr))
1760        {
1761            bail!("HAVING needs a GROUP BY or an aggregate — it filters groups, not rows; \
1762                   use WHERE to filter rows");
1763        }
1764
1765        Ok(Select {
1766            distinct,
1767            items,
1768            from,
1769            joins,
1770            where_,
1771            group_by,
1772            having,
1773            order_by: vec![],
1774            limit: None,
1775            offset: None,
1776            set_ops: vec![],
1777        })
1778    }
1779
1780    fn parse_count(&mut self, what: &str) -> Result<usize> {
1781        match self.next() {
1782            Tok::Num(n) if n >= 0.0 && n.fract() == 0.0 => Ok(n as usize),
1783            other => bail!("{} expects a non-negative integer, got {:?}", what, other),
1784        }
1785    }
1786}
1787
1788/// Keywords that begin a clause, and so can never be a bare alias.
1789///
1790/// Without this, `FROM pg_class WHERE x` parses `pg_class` aliased as
1791/// `WHERE` — and then the predicate vanishes and every row comes back.
1792fn is_clause_keyword(upper: &str) -> bool {
1793    matches!(
1794        upper,
1795        "FROM" | "WHERE" | "GROUP" | "HAVING" | "ORDER" | "LIMIT" | "OFFSET"
1796            | "JOIN" | "LEFT" | "RIGHT" | "FULL" | "INNER" | "CROSS" | "OUTER"
1797            | "ON" | "USING" | "AND" | "OR" | "AS" | "UNION" | "INTERSECT"
1798            | "EXCEPT" | "FETCH" | "FOR" | "WINDOW" | "RETURNING" | "INTO"
1799            | "ASC" | "DESC" | "NULLS" | "IS" | "IN" | "NOT" | "LIKE" | "ILIKE"
1800            | "BETWEEN" | "THEN" | "WHEN" | "ELSE" | "END" | "CASE" | "DISTINCT"
1801            | "SELECT" | "WITH" | "ALL"
1802    )
1803}
1804
1805/// Parse one `SELECT` statement — possibly a compound one.
1806pub fn parse(sql: &str) -> Result<Select> {
1807    let toks = lex(sql)?;
1808    let mut p = Parser { toks, pos: 0 };
1809    // A statement wrapped in parentheses: `(SELECT ...) UNION (SELECT ...)`.
1810    let sel = if matches!(p.peek(), Tok::Punct('(')) && p.peek_at(1).is_kw("SELECT") {
1811        p.pos += 1;
1812        let mut first = p.parse_query()?;
1813        p.expect_punct(')')?;
1814        // Set operations may follow the parenthesised head.
1815        loop {
1816            let op = if p.eat_kw("UNION") {
1817                SetOp::Union
1818            } else if p.eat_kw("INTERSECT") {
1819                SetOp::Intersect
1820            } else if p.eat_kw("EXCEPT") {
1821                SetOp::Except
1822            } else {
1823                break;
1824            };
1825            let all = p.eat_kw("ALL");
1826            if !all {
1827                let _ = p.eat_kw("DISTINCT");
1828            }
1829            let query = if p.eat_punct('(') {
1830                let q = p.parse_query()?;
1831                p.expect_punct(')')?;
1832                q
1833            } else {
1834                p.parse_select_body()?
1835            };
1836            first.set_ops.push(SetArm { op, all, query });
1837        }
1838        p.parse_query_tail(&mut first)?;
1839        first
1840    } else {
1841        p.parse_query()?
1842    };
1843    let _ = p.eat_punct(';');
1844    if !matches!(p.peek(), Tok::Eof) {
1845        bail!("unexpected trailing tokens: {:?}", p.peek());
1846    }
1847    Ok(sel)
1848}
1849
1850// ─────────────────────────────────────────────────────────────────────────────
1851// Phase 3 — the evaluator
1852// ─────────────────────────────────────────────────────────────────────────────
1853
1854/// One row of a (possibly joined) result: an ordered list of
1855/// `(binding, row-or-NULL)`.
1856///
1857/// `None` is a LEFT JOIN's unmatched side. Keeping it as `None` rather than an
1858/// empty map is what makes `n.nspname IS NULL` answer correctly for a row
1859/// that had no match — an empty map would report the column as absent, which
1860/// looks identical but loses the distinction between "no such column" and "no
1861/// matching row".
1862pub struct Bound<'a> {
1863    pub parts: Vec<(String, Option<&'a Value>)>,
1864    /// What this row may reach beyond itself — see [`EvalCtx`].
1865    pub ctx: EvalCtx<'a>,
1866}
1867
1868/// What an expression may reach beyond its own row.
1869///
1870/// Both fields exist for subqueries. The resolver is what lets a subquery
1871/// RUN from inside an expression, and `outer` is the enclosing query's row, so
1872/// `WHERE attrelid = c.oid` inside `ARRAY(SELECT ... FROM pg_attribute a ...)`
1873/// can see `c` — a correlated subquery, which is what every one of psql's
1874/// subqueries is.
1875///
1876/// Scoping follows SQL: a name resolves in the innermost query that binds it,
1877/// and only then in the enclosing one. That matters for the bare column in
1878/// `\dp`'s `WHERE oid = ANY (polroles)`: `oid` is the inner `pg_roles`
1879/// row's, `polroles` is the outer `pg_policy` row's, and neither is qualified.
1880#[derive(Clone, Copy, Default)]
1881pub struct EvalCtx<'a> {
1882    pub resolver: Option<&'a Resolver<'a>>,
1883    pub outer: Option<&'a Bound<'a>>,
1884}
1885
1886impl<'a> Bound<'a> {
1887    /// A row with no enclosing scope and no way to run a subquery.
1888    pub fn new(parts: Vec<(String, Option<&'a Value>)>) -> Self {
1889        Bound { parts, ctx: EvalCtx::default() }
1890    }
1891
1892    /// Resolve a column reference.
1893    ///
1894    /// A qualified name looks only at its own binding, then at the enclosing
1895    /// query's. A bare name scans the bindings in order and takes the first
1896    /// that actually HAS the key — which is how SQL resolves an unambiguous
1897    /// bare column across a join — and falls back to the enclosing query.
1898    fn column(&self, qual: Option<&str>, name: &str) -> Value {
1899        match qual {
1900            Some(q) => {
1901                for (binding, row) in &self.parts {
1902                    if binding.eq_ignore_ascii_case(q) {
1903                        return row
1904                            .and_then(|r| r.get(name))
1905                            .cloned()
1906                            .unwrap_or(Value::Null);
1907                    }
1908                }
1909                match self.ctx.outer {
1910                    Some(o) if o.has_binding(q) => o.column(qual, name),
1911                    _ => Value::Null,
1912                }
1913            }
1914            None => {
1915                for (_, row) in &self.parts {
1916                    if let Some(v) = row.and_then(|r| r.get(name)) {
1917                        return v.clone();
1918                    }
1919                }
1920                match self.ctx.outer {
1921                    Some(o) => o.column(None, name),
1922                    None => Value::Null,
1923                }
1924            }
1925        }
1926    }
1927
1928    /// Is `qual` a binding in this row — or in an enclosing query's row — at
1929    /// all? Used to tell "unknown table alias" (a query bug, worth an error)
1930    /// from "column absent in this row" (ordinary schemaless behaviour, worth
1931    /// a NULL).
1932    fn has_binding(&self, qual: &str) -> bool {
1933        self.parts.iter().any(|(b, _)| b.eq_ignore_ascii_case(qual))
1934            || self.ctx.outer.is_some_and(|o| o.has_binding(qual))
1935    }
1936
1937    /// Every column of every bound row, for `SELECT *`.
1938    fn flatten(&self) -> Vec<(String, Value)> {
1939        let mut out = vec![];
1940        for (_, row) in &self.parts {
1941            if let Some(Value::Object(m)) = row {
1942                for (k, v) in m {
1943                    out.push((k.clone(), v.clone()));
1944                }
1945            }
1946        }
1947        out
1948    }
1949
1950    fn flatten_binding(&self, qual: &str) -> Vec<(String, Value)> {
1951        let mut out = vec![];
1952        for (binding, row) in &self.parts {
1953            if binding.eq_ignore_ascii_case(qual) {
1954                if let Some(Value::Object(m)) = row {
1955                    for (k, v) in m {
1956                        out.push((k.clone(), v.clone()));
1957                    }
1958                }
1959            }
1960        }
1961        out
1962    }
1963}
1964
1965/// SQL truth: three-valued. `None` is UNKNOWN.
1966///
1967/// This is not pedantry. A LEFT JOIN produces NULL columns, and a predicate
1968/// over NULL must be UNKNOWN rather than false — because `NOT UNKNOWN` is
1969/// UNKNOWN, not true. Collapsing UNKNOWN to false would make
1970/// `WHERE NOT (n.nspname = 'x')` include unmatched rows that Postgres
1971/// excludes, and the row counts would silently disagree.
1972type Truth = Option<bool>;
1973
1974fn truthy(v: &Value) -> Truth {
1975    match v {
1976        Value::Null => None,
1977        Value::Bool(b) => Some(*b),
1978        // A predicate position holding a non-boolean is a query error in
1979        // Postgres. Being lenient here would let `WHERE 1` mean something
1980        // different than it does there, so it is treated as UNKNOWN.
1981        _ => None,
1982    }
1983}
1984
1985/// Compare two values for ordering and equality.
1986///
1987/// Numbers compare numerically, strings lexicographically, booleans false <
1988/// true. A number and a numeric-looking string compare NUMERICALLY, because
1989/// catalogue rows carry oids as numbers while a client may quote them.
1990fn cmp_values(a: &Value, b: &Value) -> Option<std::cmp::Ordering> {
1991    use std::cmp::Ordering;
1992    match (a, b) {
1993        (Value::Null, _) | (_, Value::Null) => None,
1994        (Value::Number(x), Value::Number(y)) => {
1995            x.as_f64().partial_cmp(&y.as_f64())
1996        }
1997        (Value::String(x), Value::String(y)) => Some(x.cmp(y)),
1998        (Value::Bool(x), Value::Bool(y)) => Some(x.cmp(y)),
1999        // Mixed number/string: try numeric first, then fall back to text, so
2000        // `oid = '16384'` behaves the way a Postgres client expects.
2001        (Value::Number(x), Value::String(y)) => match y.parse::<f64>() {
2002            Ok(n) => x.as_f64().partial_cmp(&Some(n)),
2003            Err(_) => Some(as_text(a).cmp(&as_text(b))),
2004        },
2005        (Value::String(x), Value::Number(y)) => match x.parse::<f64>() {
2006            Ok(n) => Some(n).partial_cmp(&y.as_f64()),
2007            Err(_) => Some(as_text(a).cmp(&as_text(b))),
2008        },
2009        _ => {
2010            let (x, y) = (as_text(a), as_text(b));
2011            if x == y { Some(Ordering::Equal) } else { Some(x.cmp(&y)) }
2012        }
2013    }
2014}
2015
2016/// The text a user sees for a value — not its JSON encoding.
2017fn as_text(v: &Value) -> String {
2018    match v {
2019        Value::String(s) => s.clone(),
2020        Value::Null => String::new(),
2021        Value::Bool(b) => (if *b { "t" } else { "f" }).to_string(),
2022        // Postgres's array text form, so `polroles <> '{0}'` compares like
2023        // for like and `arr::text` reads as a client expects.
2024        Value::Array(items) => {
2025            let inner: Vec<String> = items
2026                .iter()
2027                .map(|i| match i {
2028                    Value::Null => "NULL".to_string(),
2029                    Value::String(s) if s.is_empty()
2030                        || s.chars().any(|c| c.is_whitespace() || matches!(c, ',' | '{' | '}' | '"' | '\\')) =>
2031                    {
2032                        format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
2033                    }
2034                    other => as_text(other),
2035                })
2036                .collect();
2037            format!("{{{}}}", inner.join(","))
2038        }
2039        other => other.to_string(),
2040    }
2041}
2042
2043fn num(v: &Value) -> Option<f64> {
2044    match v {
2045        Value::Number(n) => n.as_f64(),
2046        Value::String(s) => s.parse().ok(),
2047        Value::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
2048        _ => None,
2049    }
2050}
2051
2052/// Every number this engine PRODUCES goes through here, so that one rule
2053/// decides how numbers render.
2054///
2055/// An integral value becomes a JSON integer. Without this, the lexer's `f64`
2056/// leaked into the output and `SELECT 1` answered `1.0` — which a client reads
2057/// as the TEXT "1.0", where PostgreSQL says "1". The liveness probe every
2058/// driver opens with was the most visible casualty.
2059///
2060/// Note what this rule cannot do: PostgreSQL distinguishes `1` (integer) from
2061/// `1.0` (numeric with scale 1), and JSON has no numeric-with-scale type at
2062/// all, so that distinction is unrepresentable here whatever we choose.
2063/// Rendering integral values as integers is the only self-consistent option
2064/// available, and it is the one that matches the common case.
2065fn from_f64(f: f64) -> Value {
2066    if f.is_finite() && f.fract() == 0.0 && f >= i64::MIN as f64 && f <= i64::MAX as f64 {
2067        return Value::Number((f as i64).into());
2068    }
2069    serde_json::Number::from_f64(f).map(Value::Number).unwrap_or(Value::Null)
2070}
2071
2072/// Evaluate an expression against one bound row.
2073pub fn eval(e: &Expr, row: &Bound) -> Result<Value> {
2074    Ok(match e {
2075        Expr::Literal(v) => v.clone(),
2076
2077        Expr::Column { qual, name } => {
2078            // An unknown ALIAS is a query bug and is reported. An unknown
2079            // COLUMN in a known binding is NULL, because a schemaless
2080            // document may legitimately omit any field.
2081            if let Some(q) = qual {
2082                if !row.has_binding(q) {
2083                    bail!("no table or alias named {:?} in this query", q);
2084                }
2085            }
2086            row.column(qual.as_deref(), name)
2087        }
2088
2089        Expr::Cast { expr, .. } => eval(expr, row)?,
2090
2091        Expr::Star | Expr::QualifiedStar(_) => {
2092            bail!("`*` is only valid in a select list or as count(*)")
2093        }
2094
2095        Expr::Unary { op, expr } => {
2096            let v = eval(expr, row)?;
2097            match op.as_str() {
2098                "NOT" => match truthy(&v) {
2099                    // NOT UNKNOWN is UNKNOWN, not true.
2100                    None => Value::Null,
2101                    Some(b) => Value::Bool(!b),
2102                },
2103                "-" => match num(&v) {
2104                    Some(n) => from_f64(-n),
2105                    None => Value::Null,
2106                },
2107                other => bail!("unsupported unary operator {:?}", other),
2108            }
2109        }
2110
2111        Expr::Binary { op, left, right } => {
2112            // AND / OR short-circuit on the value that decides the result, and
2113            // follow SQL's three-valued truth tables:
2114            //   false AND unknown = false      true  OR unknown = true
2115            //   true  AND unknown = unknown    false OR unknown = unknown
2116            if op == "AND" {
2117                let l = truthy(&eval(left, row)?);
2118                if l == Some(false) {
2119                    return Ok(Value::Bool(false));
2120                }
2121                let r = truthy(&eval(right, row)?);
2122                return Ok(match (l, r) {
2123                    (_, Some(false)) => Value::Bool(false),
2124                    (Some(true), Some(true)) => Value::Bool(true),
2125                    _ => Value::Null,
2126                });
2127            }
2128            if op == "OR" {
2129                let l = truthy(&eval(left, row)?);
2130                if l == Some(true) {
2131                    return Ok(Value::Bool(true));
2132                }
2133                let r = truthy(&eval(right, row)?);
2134                return Ok(match (l, r) {
2135                    (_, Some(true)) => Value::Bool(true),
2136                    (Some(false), Some(false)) => Value::Bool(false),
2137                    _ => Value::Null,
2138                });
2139            }
2140
2141            let l = eval(left, row)?;
2142            let r = eval(right, row)?;
2143            apply_op(op, l, r)?
2144        }
2145
2146        Expr::IsNull { expr, negated } => {
2147            let v = eval(expr, row)?;
2148            // `IS NULL` is the one predicate that is never UNKNOWN — it always
2149            // answers true or false, which is exactly why it exists.
2150            Value::Bool(v.is_null() != *negated)
2151        }
2152
2153        Expr::InList { expr, list, negated } => {
2154            let v = eval(expr, row)?;
2155            if v.is_null() {
2156                return Ok(Value::Null);
2157            }
2158            let mut items = Vec::with_capacity(list.len());
2159            for item in list {
2160                items.push(eval(item, row)?);
2161            }
2162            in_values(&v, &items, *negated)?
2163        }
2164
2165        // ── subqueries ──────────────────────────────────────────────────────
2166        Expr::Subquery(q) => {
2167            let (cols, rows) = run_sub(q, row)?;
2168            if cols.len() != 1 {
2169                bail!("a subquery used as an expression must return exactly one \
2170                       column, this one returns {}", cols.len());
2171            }
2172            match rows.len() {
2173                0 => Value::Null,
2174                1 => rows[0].get(&cols[0].key).cloned().unwrap_or(Value::Null),
2175                n => bail!("more than one row returned by a subquery used as an \
2176                            expression ({} rows)", n),
2177            }
2178        }
2179        Expr::Exists { query, negated } => {
2180            let (_, rows) = run_sub(query, row)?;
2181            Value::Bool(!rows.is_empty() != *negated)
2182        }
2183        Expr::ArrayQuery(q) => Value::Array(first_column(q, row)?),
2184        Expr::InSubquery { expr, query, negated } => {
2185            let v = eval(expr, row)?;
2186            if v.is_null() {
2187                return Ok(Value::Null);
2188            }
2189            let items = first_column(query, row)?;
2190            in_values(&v, &items, *negated)?
2191        }
2192        Expr::Quantified { op, left, all, right } => {
2193            let l = eval(left, row)?;
2194            let r = eval(right, row)?;
2195            let items = match r {
2196                Value::Null => return Ok(Value::Null),
2197                Value::Array(items) => items,
2198                other => bail!(
2199                    "{} requires an array or a subquery on its right side, got {}",
2200                    if *all { "ALL" } else { "ANY" },
2201                    as_text(&other)
2202                ),
2203            };
2204            // ANY: true if any element compares true; false if all compare
2205            // false; else UNKNOWN. ALL is the dual. An empty array is false
2206            // for ANY and true for ALL, as SQL says.
2207            let mut saw_true = false;
2208            let mut saw_false = false;
2209            let mut saw_null = false;
2210            for item in items {
2211                match truthy(&apply_op(op, l.clone(), item)?) {
2212                    Some(true) => saw_true = true,
2213                    Some(false) => saw_false = true,
2214                    None => saw_null = true,
2215                }
2216            }
2217            if *all {
2218                if saw_false {
2219                    Value::Bool(false)
2220                } else if saw_null {
2221                    Value::Null
2222                } else {
2223                    Value::Bool(true)
2224                }
2225            } else if saw_true {
2226                Value::Bool(true)
2227            } else if saw_null {
2228                Value::Null
2229            } else {
2230                Value::Bool(false)
2231            }
2232        }
2233        Expr::Index { expr, index } => {
2234            let arr = eval(expr, row)?;
2235            let i = eval(index, row)?;
2236            match (arr, num(&i)) {
2237                (Value::Array(items), Some(n)) if n >= 1.0 => {
2238                    items.get(n as usize - 1).cloned().unwrap_or(Value::Null)
2239                }
2240                _ => Value::Null,
2241            }
2242        }
2243        Expr::ArrayLit(items) => {
2244            let mut out = Vec::with_capacity(items.len());
2245            for i in items {
2246                out.push(eval(i, row)?);
2247            }
2248            Value::Array(out)
2249        }
2250
2251        Expr::Case { operand, whens, else_ } => {
2252            let subject = match operand {
2253                Some(o) => Some(eval(o, row)?),
2254                None => None,
2255            };
2256            for (cond, then) in whens {
2257                let hit = match &subject {
2258                    // simple CASE: compare the operand to each WHEN value.
2259                    Some(sv) => {
2260                        let cv = eval(cond, row)?;
2261                        matches!(cmp_values(sv, &cv), Some(std::cmp::Ordering::Equal))
2262                    }
2263                    // searched CASE: each WHEN is a predicate, and UNKNOWN
2264                    // does not match.
2265                    None => truthy(&eval(cond, row)?) == Some(true),
2266                };
2267                if hit {
2268                    return eval(then, row);
2269                }
2270            }
2271            match else_ {
2272                Some(e) => eval(e, row)?,
2273                // A CASE with no matching branch and no ELSE is NULL, which is
2274                // exactly what psql's \dt relies on for an unknown relkind.
2275                None => Value::Null,
2276            }
2277        }
2278
2279        Expr::Func { name, args } => eval_func(name, args, row)?,
2280
2281        // An aggregate has no value for a single row — it is reduced over a
2282        // GROUP by the executor and replaced with a literal before the rest of
2283        // the expression is evaluated. Reaching here means a grouped statement
2284        // took an ungrouped path, which is a bug in this engine rather than in
2285        // the query, so it says so instead of inventing a number.
2286        Expr::Agg { name, .. } => bail!(
2287            "{}() is an aggregate and has no value for one row — it is reduced \
2288             over a GROUP. Reaching this point is an engine bug, not a problem \
2289             with the query",
2290            name
2291        ),
2292    })
2293}
2294
2295/// `x [NOT] IN (values)` over already-evaluated values.
2296fn in_values(v: &Value, items: &[Value], negated: bool) -> Result<Value> {
2297    let mut any_null = false;
2298    let mut found = false;
2299    for iv in items {
2300        if iv.is_null() {
2301            any_null = true;
2302            continue;
2303        }
2304        if matches!(cmp_values(v, iv), Some(std::cmp::Ordering::Equal)) {
2305            found = true;
2306            break;
2307        }
2308    }
2309    // `x NOT IN (1, NULL)` is UNKNOWN rather than true when x is not 1 —
2310    // because x might equal the NULL. Postgres agrees, and this is the
2311    // classic NOT IN trap.
2312    Ok(if found {
2313        Value::Bool(!negated)
2314    } else if any_null {
2315        Value::Null
2316    } else {
2317        Value::Bool(negated)
2318    })
2319}
2320
2321/// Run a subquery in the scope of `row`.
2322///
2323/// The row is the subquery's OUTER scope: its own relations bind first, and
2324/// anything they do not bind resolves against `row`. That is a correlated
2325/// subquery, evaluated the direct way — once per outer row. Honest for the
2326/// catalogue relations this engine serves (tens of rows squared), and the
2327/// executor refuses to route a large collection through it.
2328fn run_sub(q: &Select, row: &Bound) -> Result<(Vec<OutCol>, Vec<Value>)> {
2329    let Some(resolve) = row.ctx.resolver else {
2330        bail!("a subquery cannot run here: this evaluation has no relation resolver");
2331    };
2332    let (cols, rows, _) = execute_inner(q, resolve, Opts::default(), Some(row))?;
2333    Ok((cols, rows))
2334}
2335
2336/// The first column of a subquery's every row — what `ARRAY(SELECT ...)`,
2337/// `IN (SELECT ...)` and `= ANY (SELECT ...)` all consume.
2338fn first_column(q: &Select, row: &Bound) -> Result<Vec<Value>> {
2339    let (cols, rows) = run_sub(q, row)?;
2340    let Some(first) = cols.first() else {
2341        bail!("the subquery returns no columns");
2342    };
2343    Ok(rows
2344        .into_iter()
2345        .map(|r| r.get(&first.key).cloned().unwrap_or(Value::Null))
2346        .collect())
2347}
2348
2349/// A binary operator over two evaluated operands. Shared by `Expr::Binary`
2350/// and the element-wise `ANY` / `ALL`, so the two cannot disagree about what
2351/// `=` means.
2352fn apply_op(op: &str, l: Value, r: Value) -> Result<Value> {
2353    // Every comparison over NULL is UNKNOWN — including `NULL = NULL`.
2354    let compare = |ord: fn(std::cmp::Ordering) -> bool| -> Value {
2355        match cmp_values(&l, &r) {
2356            None => Value::Null,
2357            Some(o) => Value::Bool(ord(o)),
2358        }
2359    };
2360
2361    Ok(match op {
2362        "IS DISTINCT FROM" | "IS NOT DISTINCT FROM" => {
2363            let distinct = match (l.is_null(), r.is_null()) {
2364                (true, true) => false,
2365                (true, false) | (false, true) => true,
2366                (false, false) => !matches!(cmp_values(&l, &r), Some(std::cmp::Ordering::Equal)),
2367            };
2368            Value::Bool(distinct != op.starts_with("IS NOT"))
2369        }
2370        "=" => compare(|o| o.is_eq()),
2371                "!=" | "<>" => compare(|o| o.is_ne()),
2372                "<" => compare(|o| o.is_lt()),
2373                "<=" => compare(|o| o.is_le()),
2374                ">" => compare(|o| o.is_gt()),
2375                ">=" => compare(|o| o.is_ge()),
2376
2377                "~" | "~*" | "!~" | "!~*" => {
2378                    if l.is_null() || r.is_null() {
2379                        Value::Null
2380                    } else {
2381                        let pat = as_text(&r);
2382                        if let Some(why) = crate::nql::regex_error_pub(&pat) {
2383                            bail!(
2384                                "{} — in {:?}. The supported subset is ^ $ . | ( ) \
2385                                 [ ] * + ? and literal text",
2386                                why, pat
2387                            );
2388                        }
2389                        let hit = crate::nql::regex_match_pub(
2390                            &as_text(&l), &pat, op.ends_with('*'));
2391                        Value::Bool(hit != op.starts_with('!'))
2392                    }
2393                }
2394
2395                _ if op.starts_with("LIKE") || op.starts_with("ILIKE")
2396                     || op.starts_with("NOT LIKE") || op.starts_with("NOT ILIKE") => {
2397                    if l.is_null() || r.is_null() {
2398                        Value::Null
2399                    } else {
2400                        // "NOT ILIKE ESCAPE /" -> base "NOT ILIKE", escape '/'
2401                        let (base, esc): (&str, Option<char>) =
2402                            match op.split_once(" ESCAPE ") {
2403                                Some((b, e)) => (b, e.chars().next()),
2404                                None => (&op[..], None),
2405                            };
2406                        let ci = base.ends_with("ILIKE");
2407                        let txt = as_text(&l);
2408                        let pat = as_text(&r);
2409                        let hit = match esc {
2410                            Some(c) => crate::nql::like_match_escape_pub(&txt, &pat, ci, c),
2411                            None => crate::nql::like_match_pub(&txt, &pat, ci),
2412                        };
2413                        Value::Bool(hit != base.starts_with("NOT"))
2414                    }
2415                }
2416
2417                // String concatenation. NULL propagates, as in Postgres.
2418                "||" => {
2419                    if l.is_null() || r.is_null() {
2420                        Value::Null
2421                    } else {
2422                        Value::String(format!("{}{}", as_text(&l), as_text(&r)))
2423                    }
2424                }
2425
2426                "+" | "-" | "*" | "/" | "%" => match (num(&l), num(&r)) {
2427                    (Some(a), Some(b)) => match op {
2428                        "+" => from_f64(a + b),
2429                        "-" => from_f64(a - b),
2430                        "*" => from_f64(a * b),
2431                        // Division by zero is an ERROR in Postgres, not
2432                        // infinity. Returning inf would be a wrong number.
2433                        "/" if b == 0.0 => bail!("division by zero"),
2434                        "/" => from_f64(a / b),
2435                        "%" if b == 0.0 => bail!("division by zero"),
2436                        "%" => from_f64(a % b),
2437                        _ => unreachable!(),
2438                    },
2439                    _ => Value::Null,
2440                },
2441
2442                other => bail!("unsupported operator {:?}", other),
2443    })
2444}
2445
2446/// Scalar functions.
2447///
2448/// Only what real clients actually call. An unknown function is REFUSED by
2449/// name rather than returning NULL — a NULL would flow into a result set as a
2450/// blank column and look like missing data rather than a missing feature.
2451fn eval_func(name: &str, args: &[Expr], row: &Bound) -> Result<Value> {
2452    // Evaluated lazily per arm, because `coalesce` must not error on a later
2453    // argument once an earlier one is non-null.
2454    let arg = |i: usize| -> Result<Value> {
2455        match args.get(i) {
2456            Some(e) => eval(e, row),
2457            None => Ok(Value::Null),
2458        }
2459    };
2460
2461    Ok(match name {
2462        // ── identity / session ──────────────────────────────────────────────
2463        // NEDB presents a single role and a single schema; reporting them
2464        // consistently is what lets a client's "who am I" probe succeed.
2465        "pg_get_userbyid" | "current_user" | "session_user" | "user" => {
2466            Value::String("nedb".into())
2467        }
2468        "current_schema" => Value::String("public".into()),
2469        "current_database" | "current_catalog" => Value::String("nedb".into()),
2470        "version" => Value::String(crate::pgwire::version_string()),
2471
2472        // ── visibility ──────────────────────────────────────────────────────
2473        // Every relation NEDB reports is in `public` and reachable on the
2474        // search path, so visibility is unconditionally true. Returning false
2475        // would hide every table from `\dt`.
2476        "pg_table_is_visible" | "pg_type_is_visible" | "pg_function_is_visible"
2477        | "pg_opclass_is_visible" | "pg_conversion_is_visible" => Value::Bool(true),
2478
2479        // ── encoding ────────────────────────────────────────────────────────
2480        "pg_encoding_to_char" => Value::String("UTF8".into()),
2481        "pg_get_expr" | "pg_get_indexdef" | "pg_get_constraintdef"
2482        | "pg_get_viewdef" | "pg_get_partkeydef" | "obj_description"
2483        | "col_description" | "shobj_description" => Value::Null,
2484
2485        // ── text ────────────────────────────────────────────────────────────
2486        "lower" => match arg(0)? {
2487            Value::Null => Value::Null,
2488            v => Value::String(as_text(&v).to_lowercase()),
2489        },
2490        "upper" => match arg(0)? {
2491            Value::Null => Value::Null,
2492            v => Value::String(as_text(&v).to_uppercase()),
2493        },
2494        "length" | "char_length" | "character_length" => match arg(0)? {
2495            Value::Null => Value::Null,
2496            v => from_f64(as_text(&v).chars().count() as f64),
2497        },
2498        "format_type" => match arg(0)? {
2499            Value::Null => Value::Null,
2500            v => Value::String(crate::pgcatalog::type_name_pub(
2501                num(&v).unwrap_or(25.0) as i32).to_string()),
2502        },
2503        "array_to_string" | "pg_catalog.array_to_string" => {
2504            // NEDB stores no arrays in the catalogue, so an ACL column is
2505            // NULL and joining it yields NULL — the same as Postgres for a
2506            // relation with default privileges.
2507            match arg(0)? {
2508                Value::Array(items) => {
2509                    let sep = as_text(&arg(1)?);
2510                    Value::String(
2511                        items.iter().map(as_text).collect::<Vec<_>>().join(&sep),
2512                    )
2513                }
2514                _ => Value::Null,
2515            }
2516        }
2517        "quote_ident" => Value::String(as_text(&arg(0)?)),
2518        "quote_literal" => Value::String(format!("'{}'", as_text(&arg(0)?).replace('\'', "''"))),
2519        // `format('%s FROM %s', a, b)` — psql's `\dX` builds a definition
2520        // with it. `%s` is text, `%I` an identifier, `%L` a quoted literal;
2521        // anything else is refused rather than passed through as garbage.
2522        "format" => {
2523            let fmt = as_text(&arg(0)?);
2524            let mut out = String::new();
2525            let mut next = 1usize;
2526            let mut chars = fmt.chars().peekable();
2527            while let Some(c) = chars.next() {
2528                if c != '%' {
2529                    out.push(c);
2530                    continue;
2531                }
2532                match chars.next() {
2533                    Some('%') => out.push('%'),
2534                    Some(spec @ ('s' | 'I' | 'L')) => {
2535                        let v = arg(next)?;
2536                        next += 1;
2537                        match (spec, &v) {
2538                            ('L', Value::Null) => out.push_str("NULL"),
2539                            ('L', v) => out.push_str(&format!("'{}'", as_text(v).replace('\'', "''"))),
2540                            (_, v) => out.push_str(&as_text(v)),
2541                        }
2542                    }
2543                    other => bail!("format(): unsupported conversion %{}", other.map(String::from).unwrap_or_default()),
2544                }
2545            }
2546            Value::String(out)
2547        }
2548
2549        // ── arrays ──────────────────────────────────────────────────────────
2550        // NULL for a NULL or empty array, as Postgres answers — which is what
2551        // makes psql's `CASE WHEN array_length(acl, 1) = 0` fall to its ELSE.
2552        "array_length" | "array_upper" | "cardinality" => match arg(0)? {
2553            Value::Array(items) if !items.is_empty() => from_f64(items.len() as f64),
2554            Value::Array(_) if name == "cardinality" => from_f64(0.0),
2555            _ => Value::Null,
2556        },
2557        "array_lower" => match arg(0)? {
2558            Value::Array(items) if !items.is_empty() => from_f64(1.0),
2559            _ => Value::Null,
2560        },
2561
2562        // ── sizes ───────────────────────────────────────────────────────────
2563        // NEDB does not track a per-collection on-disk size the way Postgres
2564        // tracks a heap's, and inventing one would be a plausible number that
2565        // is wrong. NULL renders as a blank cell in `\dt+`, which is the
2566        // truthful "not known" — the same policy the catalogue module states
2567        // for statistics.
2568        "pg_table_size" | "pg_total_relation_size" | "pg_relation_size"
2569        | "pg_indexes_size" | "pg_database_size" => Value::Null,
2570        "pg_size_pretty" => match num(&arg(0)?) {
2571            None => Value::Null,
2572            Some(n) => {
2573                let units = ["bytes", "kB", "MB", "GB", "TB", "PB"];
2574                let mut v = n;
2575                let mut u = 0usize;
2576                while v.abs() >= 10240.0 && u + 1 < units.len() {
2577                    v /= 1024.0;
2578                    u += 1;
2579                }
2580                Value::String(format!("{} {}", v.round() as i64, units[u]))
2581            }
2582        },
2583
2584        // ── more definition getters, all honestly NULL ──────────────────────
2585        // NEDB has no triggers, rules, statistics objects, functions or
2586        // publications, so every relation these are called on is empty and
2587        // the call is never reached with a real row. NULL keeps the query
2588        // shape valid without fabricating a definition.
2589        "pg_get_triggerdef" | "pg_get_ruledef" | "pg_get_statisticsobjdef"
2590        | "pg_get_statisticsobjdef_columns" | "pg_get_function_result"
2591        | "pg_get_function_arguments" | "pg_get_function_identity_arguments"
2592        | "pg_get_functiondef" | "pg_get_serial_sequence" | "pg_get_partition_constraintdef"
2593        | "pg_relation_filepath" | "pg_tablespace_location" => Value::Null,
2594        "pg_relation_is_publishable" => Value::Bool(true),
2595        "pg_statistics_obj_is_visible" | "pg_opfamily_is_visible" | "pg_collation_is_visible"
2596        | "pg_ts_config_is_visible" | "pg_ts_dict_is_visible" | "pg_ts_parser_is_visible"
2597        | "pg_ts_template_is_visible" | "has_table_privilege" | "has_schema_privilege"
2598        | "has_database_privilege" | "pg_has_role" => Value::Bool(true),
2599        // The settings a driver or psql actually asks for. Anything else is
2600        // refused by name, exactly as Postgres refuses an unrecognised one.
2601        "current_setting" => match arg(0)? {
2602            Value::Null => Value::Null,
2603            v => match as_text(&v).to_lowercase().as_str() {
2604                "server_version" => Value::String(crate::pgwire::version_string()),
2605                "server_encoding" | "client_encoding" => Value::String("UTF8".into()),
2606                "standard_conforming_strings" | "integer_datetimes" | "is_superuser" => {
2607                    Value::String("on".into())
2608                }
2609                "timezone" | "log_timezone" => Value::String("UTC".into()),
2610                "search_path" => Value::String("\"$user\", public".into()),
2611                "intervalstyle" => Value::String("postgres".into()),
2612                "datestyle" => Value::String("ISO, MDY".into()),
2613                "session_authorization" => Value::String("nedb".into()),
2614                "application_name" | "default_transaction_read_only" => Value::String(String::new()),
2615                "transaction_isolation" | "default_transaction_isolation" => {
2616                    Value::String("read committed".into())
2617                }
2618                "max_identifier_length" => Value::String("63".into()),
2619                other => {
2620                    // `current_setting(name, true)` returns NULL for a
2621                    // missing setting instead of erroring.
2622                    if truthy(&arg(1)?) == Some(true) {
2623                        Value::Null
2624                    } else {
2625                        bail!("unrecognized configuration parameter \"{}\"", other)
2626                    }
2627                }
2628            },
2629        },
2630        "pg_backend_pid" => from_f64(std::process::id() as f64),
2631        "pg_is_in_recovery" => Value::Bool(false),
2632        "txid_current" => from_f64(0.0),
2633        "now" | "current_timestamp" | "statement_timestamp" | "clock_timestamp" => {
2634            Value::String(now_iso())
2635        }
2636        "to_char" => match arg(0)? {
2637            Value::Null => Value::Null,
2638            v => Value::String(as_text(&v)),
2639        },
2640        "generate_series" | "unnest" => bail!(
2641            "{}() returns a set of rows — write it in FROM, not in the select list", name
2642        ),
2643
2644        // ── null handling ───────────────────────────────────────────────────
2645        "coalesce" => {
2646            let mut out = Value::Null;
2647            for a in args {
2648                let v = eval(a, row)?;
2649                if !v.is_null() {
2650                    out = v;
2651                    break;
2652                }
2653            }
2654            out
2655        }
2656        "nullif" => {
2657            let a = arg(0)?;
2658            let b = arg(1)?;
2659            if matches!(cmp_values(&a, &b), Some(std::cmp::Ordering::Equal)) {
2660                Value::Null
2661            } else {
2662                a
2663            }
2664        }
2665
2666        // ── casts spelled as functions ──────────────────────────────────────
2667        "int4" | "int8" | "int2" => match num(&arg(0)?) {
2668            Some(n) => from_f64(n.trunc()),
2669            None => Value::Null,
2670        },
2671        "text" => match arg(0)? {
2672            Value::Null => Value::Null,
2673            v => Value::String(as_text(&v)),
2674        },
2675
2676        other if is_aggregate(other) => bail!(
2677            "{}() is an aggregate, which is only meaningful over a whole result set — \
2678             it is evaluated by the executor, never per row",
2679            other
2680        ),
2681
2682        other => bail!(
2683            "the function {}() is not implemented. It is refused rather than \
2684             answered with NULL, because a NULL column reads as missing DATA \
2685             rather than a missing feature",
2686            other
2687        ),
2688    })
2689}
2690
2691/// An ISO-8601 wall-clock timestamp, for the handful of clients that ask.
2692fn now_iso() -> String {
2693    let secs = std::time::SystemTime::now()
2694        .duration_since(std::time::UNIX_EPOCH)
2695        .map(|d| d.as_secs())
2696        .unwrap_or(0);
2697    // Civil-from-days (Howard Hinnant's algorithm), UTC.
2698    let days = (secs / 86_400) as i64;
2699    let rem = secs % 86_400;
2700    let z = days + 719_468;
2701    let era = z.div_euclid(146_097);
2702    let doe = z.rem_euclid(146_097);
2703    let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
2704    let y = yoe + era * 400;
2705    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
2706    let mp = (5 * doy + 2) / 153;
2707    let d = doy - (153 * mp + 2) / 5 + 1;
2708    let m = if mp < 10 { mp + 3 } else { mp - 9 };
2709    let y = if m <= 2 { y + 1 } else { y };
2710    format!("{:04}-{:02}-{:02} {:02}:{:02}:{:02}+00", y, m, d, rem / 3600, (rem % 3600) / 60, rem % 60)
2711}
2712
2713// ─────────────────────────────────────────────────────────────────────────────
2714// Aggregates without GROUP BY
2715// ─────────────────────────────────────────────────────────────────────────────
2716
2717const AGGREGATES: &[&str] = &[
2718    "count", "sum", "avg", "min", "max", "string_agg", "array_agg", "bool_and",
2719    "bool_or", "every",
2720];
2721
2722fn is_aggregate(name: &str) -> bool {
2723    AGGREGATES.iter().any(|a| a.eq_ignore_ascii_case(name))
2724}
2725
2726/// Does this expression call an aggregate at ITS level — not inside a
2727/// subquery, whose aggregates belong to the subquery?
2728pub fn has_aggregate(e: &Expr) -> bool {
2729    match e {
2730        Expr::Agg { .. } => true,
2731        Expr::Func { args, .. } => args.iter().any(has_aggregate),
2732        Expr::Binary { left, right, .. } => has_aggregate(left) || has_aggregate(right),
2733        Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
2734            has_aggregate(expr)
2735        }
2736        Expr::InList { expr, list, .. } => has_aggregate(expr) || list.iter().any(has_aggregate),
2737        Expr::Case { operand, whens, else_ } => {
2738            operand.as_deref().is_some_and(has_aggregate)
2739                || whens.iter().any(|(c, t)| has_aggregate(c) || has_aggregate(t))
2740                || else_.as_deref().is_some_and(has_aggregate)
2741        }
2742        Expr::Quantified { left, right, .. } => has_aggregate(left) || has_aggregate(right),
2743        Expr::Index { expr, index } => has_aggregate(expr) || has_aggregate(index),
2744        Expr::ArrayLit(items) => items.iter().any(has_aggregate),
2745        Expr::InSubquery { expr, .. } => has_aggregate(expr),
2746        Expr::Subquery(_) | Expr::Exists { .. } | Expr::ArrayQuery(_) => false,
2747        Expr::Column { .. } | Expr::Literal(_) | Expr::Star | Expr::QualifiedStar(_) => false,
2748    }
2749}
2750
2751/// Reduce one aggregate call over the rows of ONE group.
2752///
2753/// `order_by` sorts the group's rows before the values are collected, which is
2754/// the whole point of `array_agg(col ORDER BY ord)`: the array is a column
2755/// list and its ORDER is the answer. `distinct` de-duplicates the collected
2756/// values, not the rows.
2757fn aggregate(
2758    name: &str,
2759    args: &[Expr],
2760    order_by: &[OrderBy],
2761    distinct: bool,
2762    rows: &[JoinedRow],
2763    ctx: EvalCtx,
2764) -> Result<Value> {
2765    let lname = name.to_lowercase();
2766
2767    // The aggregate's own ORDER BY. Keys are precomputed so the comparator
2768    // cannot fail halfway through and leave a half-sorted group behind.
2769    let ordered: Vec<JoinedRow> = if order_by.is_empty() {
2770        rows.to_vec()
2771    } else {
2772        let mut keyed: Vec<(Vec<Value>, JoinedRow)> = Vec::with_capacity(rows.len());
2773        for r in rows {
2774            let b = bind(r, ctx);
2775            let mut key = vec![];
2776            for ob in order_by {
2777                // An ordinal inside an aggregate has no select list to index,
2778                // so it is refused rather than silently ignored.
2779                match (&ob.expr, ob.ordinal) {
2780                    (Some(e), _) => key.push(eval(e, &b)?),
2781                    (None, Some(n)) => bail!(
2782                        "ORDER BY {} inside an aggregate refers to a select-list \
2783                         position, which an aggregate does not have — name the \
2784                         column instead", n
2785                    ),
2786                    (None, None) => key.push(Value::Null),
2787                }
2788            }
2789            keyed.push((key, r.clone()));
2790        }
2791        keyed.sort_by(|a, b| sort_keys(&a.0, &b.0, order_by));
2792        keyed.into_iter().map(|(_, r)| r).collect()
2793    };
2794    let rows: &[JoinedRow] = &ordered;
2795
2796    // `count(*)` and a bare `count()` count rows; everything else evaluates
2797    // its first argument per row and skips NULLs, as SQL aggregates do.
2798    if lname == "count" && (args.is_empty() || matches!(args[0], Expr::Star)) {
2799        return Ok(from_f64(rows.len() as f64));
2800    }
2801    let Some(target) = args.first() else {
2802        bail!("{}() needs an argument", name);
2803    };
2804    let mut vals: Vec<Value> = Vec::with_capacity(rows.len());
2805    let mut all_vals: Vec<Value> = Vec::with_capacity(rows.len());
2806    for r in rows {
2807        let v = eval(target, &bind(r, ctx))?;
2808        if !v.is_null() {
2809            vals.push(v.clone());
2810        }
2811        all_vals.push(v);
2812    }
2813    if distinct {
2814        let mut seen: Vec<String> = vec![];
2815        vals.retain(|v| {
2816            let k = format!("{:?}", v);
2817            if seen.contains(&k) { false } else { seen.push(k); true }
2818        });
2819        let mut seen2: Vec<String> = vec![];
2820        all_vals.retain(|v| {
2821            let k = format!("{:?}", v);
2822            if seen2.contains(&k) { false } else { seen2.push(k); true }
2823        });
2824    }
2825    Ok(match lname.as_str() {
2826        "count" => from_f64(vals.len() as f64),
2827        "sum" | "avg" => {
2828            let nums: Vec<f64> = vals.iter().filter_map(num).collect();
2829            if nums.is_empty() {
2830                Value::Null
2831            } else if lname == "sum" {
2832                from_f64(nums.iter().sum())
2833            } else {
2834                from_f64(nums.iter().sum::<f64>() / nums.len() as f64)
2835            }
2836        }
2837        "min" | "max" => {
2838            let mut best: Option<Value> = None;
2839            for v in vals {
2840                best = Some(match best {
2841                    None => v,
2842                    Some(b) => {
2843                        let take = match cmp_values(&v, &b) {
2844                            Some(o) if lname == "min" => o.is_lt(),
2845                            Some(o) => o.is_gt(),
2846                            None => false,
2847                        };
2848                        if take { v } else { b }
2849                    }
2850                });
2851            }
2852            best.unwrap_or(Value::Null)
2853        }
2854        "string_agg" => {
2855            if vals.is_empty() {
2856                Value::Null
2857            } else {
2858                // The separator is a constant in every real call, so it is
2859                // evaluated once with no row in scope.
2860                let sep = match args.get(1) {
2861                    Some(e) => as_text(&eval(e, &Bound { parts: vec![], ctx })?),
2862                    None => String::new(),
2863                };
2864                Value::String(vals.iter().map(as_text).collect::<Vec<_>>().join(&sep))
2865            }
2866        }
2867        // array_agg keeps NULLs, as Postgres does.
2868        "array_agg" => {
2869            if all_vals.is_empty() { Value::Null } else { Value::Array(all_vals) }
2870        }
2871        "bool_and" | "every" => {
2872            if vals.is_empty() {
2873                Value::Null
2874            } else {
2875                Value::Bool(vals.iter().all(|v| truthy(v) == Some(true)))
2876            }
2877        }
2878        "bool_or" => {
2879            if vals.is_empty() {
2880                Value::Null
2881            } else {
2882                Value::Bool(vals.iter().any(|v| truthy(v) == Some(true)))
2883            }
2884        }
2885        _ => unreachable!("is_aggregate gates this"),
2886    })
2887}
2888
2889/// Replace every aggregate call in `e` with the literal it reduces to, so the
2890/// remainder can be evaluated by the ordinary evaluator against no row at all.
2891///
2892/// A column outside an aggregate has no single value across the result set,
2893/// and Postgres refuses it with the message reproduced here rather than
2894/// picking a row arbitrarily.
2895fn fold_aggregates(
2896    e: &Expr,
2897    rows: &[JoinedRow],
2898    ctx: EvalCtx,
2899    keys: &[Expr],
2900) -> Result<Expr> {
2901    let fold = |x: &Expr| fold_aggregates(x, rows, ctx, keys);
2902    Ok(match e {
2903        Expr::Agg { name, args, order_by, distinct } => {
2904            Expr::Literal(aggregate(name, args, order_by, *distinct, rows, ctx)?)
2905        }
2906        Expr::Func { name, args } => Expr::Func {
2907            name: name.clone(),
2908            args: args.iter().map(&fold).collect::<Result<_>>()?,
2909        },
2910        // A GROUP BY key is constant within its group, so it is left alone and
2911        // evaluated against any row of the group. A column that is NOT a key
2912        // has no single value there, and Postgres's own message is the one
2913        // worth reproducing — picking an arbitrary row instead is how a
2914        // grouped query returns a confidently wrong answer.
2915        Expr::Column { .. } if keys.iter().any(|k| k == e) => e.clone(),
2916        Expr::Column { qual, name } => bail!(
2917            "column \"{}{}\" must appear in the GROUP BY clause or be used in an \
2918             aggregate function",
2919            qual.as_ref().map(|q| format!("{q}.")).unwrap_or_default(),
2920            name
2921        ),
2922        Expr::Binary { op, left, right } => Expr::Binary {
2923            op: op.clone(),
2924            left: Box::new(fold(left)?),
2925            right: Box::new(fold(right)?),
2926        },
2927        Expr::Unary { op, expr } => Expr::Unary {
2928            op: op.clone(),
2929            expr: Box::new(fold(expr)?),
2930        },
2931        Expr::Cast { expr, ty } => Expr::Cast {
2932            expr: Box::new(fold(expr)?),
2933            ty: ty.clone(),
2934        },
2935        Expr::IsNull { expr, negated } => Expr::IsNull {
2936            expr: Box::new(fold(expr)?),
2937            negated: *negated,
2938        },
2939        Expr::InList { expr, list, negated } => Expr::InList {
2940            expr: Box::new(fold(expr)?),
2941            list: list.iter().map(&fold).collect::<Result<_>>()?,
2942            negated: *negated,
2943        },
2944        Expr::Case { operand, whens, else_ } => Expr::Case {
2945            operand: match operand {
2946                Some(o) => Some(Box::new(fold(o)?)),
2947                None => None,
2948            },
2949            whens: whens
2950                .iter()
2951                .map(|(c, t)| Ok((fold(c)?, fold(t)?)))
2952                .collect::<Result<_>>()?,
2953            else_: match else_ {
2954                Some(x) => Some(Box::new(fold(x)?)),
2955                None => None,
2956            },
2957        },
2958        Expr::Quantified { op, left, all, right } => Expr::Quantified {
2959            op: op.clone(),
2960            left: Box::new(fold(left)?),
2961            all: *all,
2962            right: Box::new(fold(right)?),
2963        },
2964        Expr::Index { expr, index } => Expr::Index {
2965            expr: Box::new(fold(expr)?),
2966            index: Box::new(fold(index)?),
2967        },
2968        Expr::ArrayLit(items) => Expr::ArrayLit(
2969            items.iter().map(&fold).collect::<Result<_>>()?,
2970        ),
2971        Expr::InSubquery { expr, query, negated } => Expr::InSubquery {
2972            expr: Box::new(fold(expr)?),
2973            query: query.clone(),
2974            negated: *negated,
2975        },
2976        // A bare `*` in a grouped select list is the same error as a bare
2977        // column: it names every column, none of which is a key.
2978        Expr::Star | Expr::QualifiedStar(_) => {
2979            bail!("`*` cannot be mixed with an aggregate outside count(*)")
2980        }
2981        // Constants and subqueries evaluate the same way in either mode.
2982        Expr::Literal(_) | Expr::Subquery(_) | Expr::Exists { .. } | Expr::ArrayQuery(_) => {
2983            e.clone()
2984        }
2985    })
2986}
2987
2988// ─────────────────────────────────────────────────────────────────────────────
2989// Phase 4 — execution
2990// ─────────────────────────────────────────────────────────────────────────────
2991
2992/// Every relation in a query must be addressable by a DISTINCT name.
2993///
2994/// PostgreSQL rejects `FROM a JOIN a` with "table name a specified more than
2995/// once". This engine used to accept it and answer WRONGLY: a qualified
2996/// reference scans the bindings in order and takes the first match, so both
2997/// `a.x` and `a.y` read the same row, and `FROM emp JOIN emp ON emp.mgr =
2998/// emp.id` compared every row to ITSELF and returned no rows at all.
2999///
3000/// A silently empty result is the worst possible answer — it is
3001/// indistinguishable from "there is no such data". Refusing is strictly
3002/// better, and the supported spelling is one alias per relation.
3003fn validate_bindings(sel: &Select) -> Result<()> {
3004    let mut seen: Vec<String> = vec![];
3005    if let Some(f) = &sel.from {
3006        seen.push(f.binding());
3007    }
3008    for j in &sel.joins {
3009        seen.push(j.table.binding());
3010    }
3011    for (i, b) in seen.iter().enumerate() {
3012        if let Some(prev) = seen[..i].iter().find(|p| p.eq_ignore_ascii_case(b)) {
3013            bail!(
3014                "ambiguous relation binding: {:?} appears more than once; use \
3015                 aliases (for example `FROM {} JOIN {} AS {}2 ...`)",
3016                prev, prev, prev, prev
3017            );
3018        }
3019    }
3020    Ok(())
3021}
3022
3023/// One output column: the key it is stored under, and the name the client sees.
3024///
3025/// These are NOT always the same, and that is the whole point. PostgreSQL
3026/// permits duplicate output names — `SELECT e.name, e2.name` legitimately
3027/// returns two columns both called `name`, and generated SQL relies on it.
3028/// Rows here are JSON objects, so two columns sharing a key would share a
3029/// VALUE: the second write silently overwrote the first, and the query above
3030/// returned the same value twice while reporting two columns.
3031///
3032/// So the key is made unique and the display name is left alone. Renaming the
3033/// column instead would be worse — generated SQL asks for the name it wrote.
3034#[derive(Debug, Clone, PartialEq, Eq)]
3035pub struct OutCol {
3036    pub key: String,
3037    pub name: String,
3038}
3039
3040/// A key no user field can collide with, for the second and later columns
3041/// sharing a display name. `\u{1}` is not producible in a JSON field name by
3042/// any sane writer, and the index disambiguates even if one managed it.
3043fn unique_key(taken: &[OutCol], name: &str) -> String {
3044    if !taken.iter().any(|c| c.key == name) {
3045        return name.to_string();
3046    }
3047    format!("{name}\u{1}{}", taken.len())
3048}
3049
3050/// A joined row, owned: `(binding, row-or-NULL)` per source table.
3051type JoinedRow = Vec<(String, Option<Value>)>;
3052
3053fn bind<'a>(row: &'a JoinedRow, ctx: EvalCtx<'a>) -> Bound<'a> {
3054    Bound {
3055        parts: row.iter().map(|(b, v)| (b.clone(), v.as_ref())).collect(),
3056        ctx,
3057    }
3058}
3059
3060/// The name a client sees for a select item, when no `AS` was given.
3061///
3062/// Postgres derives it: a bare column keeps its column name, a function call
3063/// takes the function's name, and anything else becomes `?column?`. Matching
3064/// that matters because clients index result columns BY NAME — psycopg's
3065/// `RealDictCursor` and every ORM do — so inventing a different name breaks
3066/// code that would work against Postgres.
3067fn derived_name(e: &Expr) -> String {
3068    match e {
3069        Expr::Column { name, .. } => name.clone(),
3070        Expr::Func { name, .. } | Expr::Agg { name, .. } => name.clone(),
3071        Expr::Cast { expr, .. } => derived_name(expr),
3072        Expr::Case { .. } => "case".to_string(),
3073        Expr::ArrayQuery(_) | Expr::ArrayLit(_) => "array".to_string(),
3074        Expr::Exists { .. } => "exists".to_string(),
3075        // A scalar subquery is named after its single output column.
3076        Expr::Subquery(q) => q
3077            .items
3078            .first()
3079            .map(|i| i.alias.clone().unwrap_or_else(|| derived_name(&i.expr)))
3080            .unwrap_or_else(|| "?column?".to_string()),
3081        _ => "?column?".to_string(),
3082    }
3083}
3084
3085/// A relation, delivered one row at a time.
3086///
3087/// # The smallest interface that permits early termination
3088///
3089/// The previous contract handed back an owned `Vec<Value>`, which forced the
3090/// whole relation to exist before any work could start. That is fine until
3091/// execution can stop early — and once `LIMIT` can stop a join, a contract
3092/// that insists on materialising 8000 rows to return 20 becomes the
3093/// bottleneck. It was measured as exactly that: after the filter fusion in
3094/// #120, the hash path's remaining time was dominated by cloning relations
3095/// rather than probing them.
3096///
3097/// So this is deliberately two methods, not an async stream and not a
3098/// borrowing iterator with a lifetime parameter threaded through the whole
3099/// evaluator. Pull a row; stop whenever you like by dropping it.
3100///
3101/// [`size_hint`](Relation::size_hint) exists only so the join planner can
3102/// keep choosing a strategy from relation sizes. A source that genuinely does
3103/// not know returns `None`, and the planner then decides from what it does
3104/// know rather than pretending.
3105pub trait Relation {
3106    /// The next row, or `None` when exhausted.
3107    fn next_row(&mut self) -> Result<Option<Value>>;
3108
3109    /// Exact row count when the source knows it, `None` when it does not.
3110    fn size_hint(&self) -> Option<usize> {
3111        None
3112    }
3113}
3114
3115/// A relation backed by an already-materialised `Vec`.
3116///
3117/// Every current caller uses this, so the interface change on its own alters
3118/// no behaviour — it is what lets the executor become demand-driven ahead of
3119/// the storage layer, rather than requiring both to move at once.
3120pub struct VecRelation {
3121    iter: std::vec::IntoIter<Value>,
3122    len: usize,
3123}
3124
3125impl Relation for VecRelation {
3126    fn next_row(&mut self) -> Result<Option<Value>> {
3127        Ok(self.iter.next())
3128    }
3129    fn size_hint(&self) -> Option<usize> {
3130        Some(self.len)
3131    }
3132}
3133
3134/// Wrap a materialised relation.
3135pub fn from_vec(rows: Vec<Value>) -> Box<dyn Relation> {
3136    let len = rows.len();
3137    Box::new(VecRelation { iter: rows.into_iter(), len })
3138}
3139
3140/// Everything one execution needs from the outside world.
3141///
3142/// A callback rather than a concrete store, which is what lets this engine
3143/// serve synthesised catalogue relations today and stored collections later
3144/// without knowing the difference.
3145pub type Resolver<'r> = dyn Fn(&str) -> Result<Option<Box<dyn Relation>>> + 'r;
3146
3147/// Run a parsed `SELECT`, returning `(column names, rows)`.
3148///
3149/// Rows come back as JSON objects keyed by output column name, which is the
3150/// shape the wire encoder already consumes.
3151pub fn execute(sel: &Select, resolve: &Resolver) -> Result<(Vec<OutCol>, Vec<Value>)> {
3152    let (cols, rows, _) = execute_explain(sel, resolve, JoinExec::Auto)?;
3153    Ok((cols, rows))
3154}
3155
3156/// Run a parsed `SELECT`, also reporting how each join was executed.
3157///
3158/// `exec` forces a join strategy, which exists so that differential tests can
3159/// drive the SAME query down BOTH paths — and so a benchmark can prove it
3160/// measured the path it claims to have measured rather than silently timing
3161/// the other one twice.
3162pub fn execute_explain(
3163    sel: &Select,
3164    resolve: &Resolver,
3165    exec: JoinExec,
3166) -> Result<(Vec<OutCol>, Vec<Value>, Plan)> {
3167    execute_with(sel, resolve, exec, true)
3168}
3169
3170/// Execution options. Every switch exists so a differential test can run the
3171/// SAME query with the optimisation on and off and compare — without that, a
3172/// test believing it exercised an optimisation could be measuring the
3173/// unoptimised path, and the equivalence suite would prove nothing.
3174#[derive(Debug, Clone, Copy)]
3175pub struct Opts {
3176    pub exec: JoinExec,
3177    pub pushdown: bool,
3178    /// Evaluate the `WHERE` clause inside the final join rather than as a
3179    /// separate pass. Semantically identical; it is what lets the row budget
3180    /// apply to a filtered join.
3181    pub fuse_filter: bool,
3182}
3183
3184impl Default for Opts {
3185    fn default() -> Self {
3186        Opts { exec: JoinExec::Auto, pushdown: true, fuse_filter: true }
3187    }
3188}
3189
3190impl Opts {
3191    pub fn exec(exec: JoinExec) -> Self {
3192        Opts { exec, ..Default::default() }
3193    }
3194}
3195
3196/// As [`execute_explain`], with predicate pushdown switchable.
3197///
3198/// The switch exists so differential tests can run the SAME query with and
3199/// without the rewrite and compare. Without it, a test believing it exercised
3200/// pushdown could be measuring the unoptimised path, and the equivalence suite
3201/// would prove nothing — the same reason `JoinExec` can force a strategy.
3202pub fn execute_with(
3203    sel: &Select,
3204    resolve: &Resolver,
3205    exec: JoinExec,
3206    pushdown: bool,
3207) -> Result<(Vec<OutCol>, Vec<Value>, Plan)> {
3208    execute_opts(sel, resolve, Opts { exec, pushdown, ..Default::default() })
3209}
3210
3211/// The full form.
3212pub fn execute_opts(
3213    sel: &Select,
3214    resolve: &Resolver,
3215    opts: Opts,
3216) -> Result<(Vec<OutCol>, Vec<Value>, Plan)> {
3217    execute_inner(sel, resolve, opts, None)
3218}
3219
3220/// A distinct-key for a projected row: its output values, in column order.
3221fn row_key(cols: &[OutCol], obj: &Map<String, Value>) -> String {
3222    cols.iter()
3223        .map(|c| format!("{:?}", obj.get(&c.key).unwrap_or(&Value::Null)))
3224        .collect::<Vec<_>>()
3225        .join("\u{1}")
3226}
3227
3228/// Combine the arms of a compound query.
3229///
3230/// Each arm runs as its own complete query, its columns are matched to the
3231/// first arm's BY POSITION (as SQL says — the names come from the first
3232/// arm), and the rows are combined per operator. `ORDER BY` / `LIMIT` then
3233/// apply to the whole, which is why the parser refused to attach them to the
3234/// last arm.
3235fn execute_set_ops<'a>(
3236    sel: &Select,
3237    resolve: &'a Resolver<'a>,
3238    opts: Opts,
3239    outer: Option<&'a Bound<'a>>,
3240) -> Result<(Vec<OutCol>, Vec<Value>, Plan)> {
3241    let ctx = EvalCtx { resolver: Some(resolve), outer };
3242    let mut head = sel.clone();
3243    head.set_ops.clear();
3244    head.order_by.clear();
3245    head.limit = None;
3246    head.offset = None;
3247    let (cols, rows, mut plan) = execute_inner(&head, resolve, opts, outer)?;
3248    let mut left: Vec<Map<String, Value>> = rows
3249        .into_iter()
3250        .map(|r| match r {
3251            Value::Object(m) => m,
3252            _ => Map::new(),
3253        })
3254        .collect();
3255
3256    for arm in &sel.set_ops {
3257        let (acols, arows, _) = execute_inner(&arm.query, resolve, opts, outer)?;
3258        let op_name = match arm.op {
3259            SetOp::Union => "UNION",
3260            SetOp::Intersect => "INTERSECT",
3261            SetOp::Except => "EXCEPT",
3262        };
3263        if acols.len() != cols.len() {
3264            bail!(
3265                "each {} query must have the same number of columns: {} vs {}",
3266                op_name, cols.len(), acols.len()
3267            );
3268        }
3269        // Positional remap onto the first arm's keys.
3270        let right: Vec<Map<String, Value>> = arows
3271            .into_iter()
3272            .map(|r| {
3273                let m = match r {
3274                    Value::Object(m) => m,
3275                    _ => Map::new(),
3276                };
3277                let mut out = Map::new();
3278                for (i, c) in cols.iter().enumerate() {
3279                    out.insert(c.key.clone(), m.get(&acols[i].key).cloned().unwrap_or(Value::Null));
3280                }
3281                out
3282            })
3283            .collect();
3284        let (nl, nr) = (left.len(), right.len());
3285        let right_keys: std::collections::HashSet<String> =
3286            right.iter().map(|m| row_key(&cols, m)).collect();
3287        let mut combined: Vec<Map<String, Value>> = match arm.op {
3288            SetOp::Union => {
3289                left.extend(right);
3290                left
3291            }
3292            SetOp::Intersect => left.into_iter().filter(|m| right_keys.contains(&row_key(&cols, m))).collect(),
3293            SetOp::Except => left.into_iter().filter(|m| !right_keys.contains(&row_key(&cols, m))).collect(),
3294        };
3295        if !arm.all {
3296            let mut seen = std::collections::HashSet::new();
3297            combined.retain(|m| seen.insert(row_key(&cols, m)));
3298        }
3299        plan.notes.push(format!(
3300            "{}{}: {} + {} rows -> {} (each arm planned separately; only the first arm's plan is shown)",
3301            op_name, if arm.all { " ALL" } else { "" }, nl, nr, combined.len()
3302        ));
3303        left = combined;
3304    }
3305
3306    // The combined rows have no source row; ORDER BY by name resolves against
3307    // the output row itself under an anonymous binding.
3308    let projected: Vec<(Map<String, Value>, JoinedRow)> = left
3309        .into_iter()
3310        .map(|m| {
3311            let src: JoinedRow = vec![(String::new(), Some(Value::Object(m.clone())))];
3312            (m, src)
3313        })
3314        .collect();
3315    let out = finish(sel, &cols, projected, ctx, &mut plan)?;
3316    Ok((cols, out, plan))
3317}
3318
3319/// One query, in an optional enclosing scope. `outer` is `Some` for a
3320/// correlated subquery and `None` at the top level.
3321fn execute_inner<'a>(
3322    sel: &Select,
3323    resolve: &'a Resolver<'a>,
3324    opts: Opts,
3325    outer: Option<&'a Bound<'a>>,
3326) -> Result<(Vec<OutCol>, Vec<Value>, Plan)> {
3327    if !sel.set_ops.is_empty() {
3328        return execute_set_ops(sel, resolve, opts, outer);
3329    }
3330    let ctx = EvalCtx { resolver: Some(resolve), outer };
3331    let exec = opts.exec;
3332    let pushdown = opts.pushdown;
3333    let mut plan = Plan::default();
3334
3335    // ── 0a. semantic validation, before any work ────────────────────────────
3336    validate_bindings(sel)?;
3337
3338    // ── 0. the row budget ───────────────────────────────────────────────────
3339    //
3340    // The only safe rewrite available without a streaming executor: when the
3341    // final answer is a PREFIX of the join's output, the join may stop as soon
3342    // as it has produced enough rows.
3343    //
3344    // Every one of these conditions is load-bearing, and each corresponds to
3345    // an operation that can REDUCE the row count after the join — capping the
3346    // join's output early would then starve it:
3347    //
3348    //   * `ORDER BY` — the prefix depends on the sort, not on emission order.
3349    //   * `DISTINCT` — deduplication can shrink 100 rows to 3.
3350    //   * a `WHERE` clause — filtering happens after the join here.
3351    //   * more than one join — an intermediate cap can starve a later join.
3352    //
3353    // `OFFSET` is added to the budget rather than disqualifying it, because
3354    // the rows skipped still have to be produced.
3355    //
3356    // This is narrow on purpose. `SELECT ... JOIN ... LIMIT n` is the shape an
3357    // interactive client sends constantly, and it was measured taking 32ms to
3358    // return 20 rows out of an 8000-row join. A wider rewrite needs a
3359    // streaming executor, not a cleverer predicate.
3360    // Fusing the `WHERE` into the final join is what makes a filtered query
3361    // eligible: the join's own output is then already filtered, so its length
3362    // is a real count of final rows and stopping early keeps a true prefix.
3363    // Without the fusion a `WHERE` had to disqualify the budget entirely.
3364    let fuse = opts.fuse_filter && sel.where_.is_some() && !sel.joins.is_empty();
3365
3366    let budget: Option<usize> = match sel.limit {
3367        Some(lim)
3368            if sel.order_by.is_empty()
3369                && !sel.distinct
3370                && !sel.joins.is_empty()
3371                && (sel.where_.is_none() || fuse) =>
3372        {
3373            Some(lim.saturating_add(sel.offset.unwrap_or(0)))
3374        }
3375        _ => None,
3376    };
3377    plan.budget = budget;
3378
3379    // ── 0b. predicate pushdown ──────────────────────────────────────────────
3380    // Conjuncts of the WHERE clause that read exactly one relation are COPIED
3381    // to pre-filter that relation before the join. The WHERE clause below is
3382    // untouched and still runs afterwards — a copy, never a move, which is
3383    // what keeps this safe for outer joins. See `sqlpush` for the argument.
3384    let all_bindings: Vec<String> = sel
3385        .from
3386        .iter()
3387        .map(|t| t.binding())
3388        .chain(sel.joins.iter().map(|j| j.table.binding()))
3389        .collect();
3390    let nullable = crate::sqlpush::nullable_bindings(sel);
3391    let push = if pushdown {
3392        crate::sqlpush::plan(sel.where_.as_ref(), &all_bindings, &nullable)
3393    } else {
3394        Pushdown::default()
3395    };
3396    plan.refusals = push.refusals.clone();
3397
3398    let mut base_scan_at: Option<usize> = None;
3399    let mut base_prefilter_at: Option<usize> = None;
3400
3401    // ── 1. source rows, and the join ────────────────────────────────────────
3402    //
3403    // The driving relation is STREAMED when there is a join to feed it into,
3404    // so a query that stops early never asks the source for the rest. The
3405    // inner side of each join is materialised, because it genuinely has to
3406    // be: a hash join builds its table before probing, and a nested loop
3407    // re-scans it per left row.
3408    let mut left_src: Box<dyn LeftSource + 'a> = match &sel.from {
3409        None => {
3410            // `SELECT 1` with no FROM is one row with no columns — which is
3411            // how a client's liveness probe is written.
3412            Box::new(VecLeft { rows: vec![vec![]], at: 0 })
3413        }
3414        Some(t) => {
3415            let rel = fetch(t, resolve, ctx)?;
3416            let binding = t.binding();
3417            // Placeholder counts, patched once the pull is over. A streamed
3418            // relation cannot report its `actual rows` before it is read, and
3419            // inventing a number would be exactly the kind of plausible
3420            // fiction `EXPLAIN` must never contain.
3421            base_scan_at = Some(plan.stages.len());
3422            plan.push(Stage::Scan {
3423                table: t.name.clone(),
3424                binding: binding.clone(),
3425                rows: 0,
3426            });
3427            let preds = push.for_binding(&binding).cloned().unwrap_or_default();
3428            if !preds.is_empty() {
3429                base_prefilter_at = Some(plan.stages.len());
3430                plan.push(Stage::Prefilter {
3431                    binding: binding.clone(),
3432                    predicates: preds.len(),
3433                    in_rows: 0,
3434                    out_rows: 0,
3435                });
3436            }
3437            Box::new(StreamLeft { rel, binding, preds, pulled: 0, kept: 0, ctx })
3438        }
3439    };
3440
3441    // The bindings accumulated so far, tracked explicitly rather than read off
3442    // the first row. Reading a row cannot describe the shape when there are no
3443    // rows — which is exactly the case a `RIGHT JOIN` onto an EMPTY left
3444    // relation produces, and it made those rows come back missing their left
3445    // bindings entirely instead of carrying them as NULL.
3446    let mut left_bindings: Vec<String> = match &sel.from {
3447        None => vec![],
3448        Some(t) => vec![t.binding()],
3449    };
3450    let last = sel.joins.len().saturating_sub(1);
3451    let mut rows: Vec<JoinedRow> = vec![];
3452    let mut base_pulled: Option<usize> = None;
3453    let mut base_kept: Option<usize> = None;
3454
3455    for (ji, join) in sel.joins.iter().enumerate() {
3456        let is_last = ji == last;
3457        let rb = join.table.binding();
3458
3459        // `LATERAL (SELECT ...)` reads the rows to its left, so it cannot be
3460        // materialised once: it runs again for every left row, in that row's
3461        // scope. A nested loop by definition, and reported as one.
3462        if join.table.lateral {
3463            let post = if fuse && is_last { sel.where_.as_ref() } else { None };
3464            let join_budget = if is_last { budget } else { None };
3465            let (out, removed, consumed, produced) = join_lateral(
3466                left_src.as_mut(), join, resolve, join_budget, post, ctx,
3467            )?;
3468            plan.push(Stage::Scan { table: join.table.name.clone(), binding: rb.clone(), rows: produced });
3469            plan.push(Stage::Join {
3470                kind: join.kind,
3471                table: join.table.name.clone(),
3472                binding: rb.clone(),
3473                strategy: Strategy::NestedLoop,
3474                keys: 0,
3475                left_rows: consumed,
3476                right_rows: produced,
3477                out_rows: out.len(),
3478                early_stopped: join_budget.is_some_and(|b| out.len() >= b),
3479                post_filter_removed: post.map(|_| removed),
3480            });
3481            plan.notes.push(format!("LATERAL {}: the subquery ran once per left row ({} times)", rb, consumed));
3482            left_bindings.push(rb);
3483            if ji == 0 {
3484                if let Some((pulled, kept)) = left_src.stats() {
3485                    base_pulled = Some(pulled);
3486                    base_kept = Some(kept);
3487                }
3488            }
3489            left_src = Box::new(VecLeft { rows: out, at: 0 });
3490            continue;
3491        }
3492
3493        let right_rel = fetch(&join.table, resolve, ctx)?;
3494        let right_all = drain(right_rel)?;
3495        plan.push(Stage::Scan {
3496            table: join.table.name.clone(),
3497            binding: rb.clone(),
3498            rows: right_all.len(),
3499        });
3500        let right_rows = prefilter(right_all, &rb, &push, &mut plan, ctx)?;
3501
3502        // The filter can only be evaluated once every binding it reads is
3503        // bound, so it fuses into the FINAL join and nowhere earlier. The
3504        // budget likewise applies only there: capping an intermediate join
3505        // can starve a later one of rows it needed.
3506        let post = if fuse && is_last { sel.where_.as_ref() } else { None };
3507        let join_budget = if is_last { budget } else { None };
3508
3509        // The planner proposes; sizes decide. A join with no provable equality
3510        // key has nothing to hash on and stays on the reference path.
3511        let keys = sqljoin::hash_keys(join.on.as_ref(), &left_bindings, &rb);
3512        let left_hint = left_src.hint().unwrap_or(usize::MAX);
3513        let strategy = sqljoin::choose(exec, keys.len(), left_hint, right_rows.len());
3514
3515        let (out, removed, consumed) = match strategy {
3516            Strategy::NestedLoop => join_nested_loop(
3517                left_src.as_mut(), &left_bindings, join, &right_rows, &rb,
3518                join_budget, post, ctx,
3519            )?,
3520            Strategy::Hash => join_hash(
3521                left_src.as_mut(), &left_bindings, join, &right_rows, &rb, &keys,
3522                join_budget, post, ctx,
3523            )?,
3524        };
3525
3526        plan.push(Stage::Join {
3527            kind: join.kind,
3528            table: join.table.name.clone(),
3529            binding: rb.clone(),
3530            strategy,
3531            keys: keys.len(),
3532            left_rows: consumed,
3533            right_rows: right_rows.len(),
3534            out_rows: out.len(),
3535            early_stopped: join_budget.is_some_and(|b| out.len() >= b),
3536            post_filter_removed: post.map(|_| removed),
3537        });
3538        left_bindings.push(rb);
3539        // Read the streamed base's counts BEFORE the source is replaced.
3540        if ji == 0 {
3541            if let Some((pulled, kept)) = left_src.stats() {
3542                base_pulled = Some(pulled);
3543                base_kept = Some(kept);
3544            }
3545        }
3546        rows = out;
3547        // The next join reads this join's output, which is already whole.
3548        left_src = Box::new(VecLeft { rows: std::mem::take(&mut rows), at: 0 });
3549    }
3550
3551    // Recover the rows from the last source, and record what the streamed
3552    // base relation actually delivered.
3553    rows = left_src.take_rows();
3554    if let Some(i) = base_scan_at {
3555        if let (Some(pulled), Some(kept)) = (base_pulled, base_kept) {
3556            if let Some(Stage::Scan { rows: r, .. }) = plan.stages.get_mut(i) {
3557                *r = pulled;
3558            }
3559            if let Some(j) = base_prefilter_at {
3560                if let Some(Stage::Prefilter { in_rows, out_rows, .. }) =
3561                    plan.stages.get_mut(j)
3562                {
3563                    *in_rows = pulled;
3564                    *out_rows = kept;
3565                }
3566            }
3567        }
3568    }
3569
3570    // ── 2. WHERE ────────────────────────────────────────────────────────────
3571    if let Some(pred) = sel.where_.as_ref().filter(|_| !fuse) {
3572        let in_rows = rows.len();
3573        let mut kept = Vec::with_capacity(rows.len());
3574        for r in rows {
3575            // Only TRUE keeps a row. UNKNOWN excludes it, which is what makes
3576            // `WHERE n.nspname <> 'x'` drop a LEFT JOIN's unmatched rows the
3577            // way Postgres does.
3578            if truthy(&eval(pred, &bind(&r, ctx))?) == Some(true) {
3579                kept.push(r);
3580            }
3581        }
3582        rows = kept;
3583        plan.push(Stage::Filter { in_rows, out_rows: rows.len() });
3584    }
3585
3586    // ── 2b. GROUP BY and aggregates ─────────────────────────────────────────
3587    //
3588    // One path serves both shapes, because an aggregate with no `GROUP BY` IS
3589    // the single-group case — `SELECT count(*) FROM t` is one group holding
3590    // every row. Writing them separately is how the two drift into disagreeing
3591    // about an empty input: `count(*)` over no rows must be 0 for the whole
3592    // table and must produce NO row at all per group when there are no groups.
3593    //
3594    // Each call is reduced over its group's rows first, then the remainder of
3595    // the expression is evaluated with those reductions already in place — so
3596    // `sum(total) / count(*)` is ordinary arithmetic over two literals by the
3597    // time it is evaluated.
3598    let grouping = !sel.group_by.is_empty();
3599    let aggregating = grouping
3600        || sel.items.iter().any(|i| has_aggregate(&i.expr))
3601        || sel.having.as_ref().is_some_and(has_aggregate);
3602    if aggregating {
3603        // Partition. First-seen order is kept so a run is reproducible; SQL
3604        // promises no group order without `ORDER BY`, and a HashMap's would
3605        // change between runs of the same query on the same data.
3606        let mut groups: Vec<(Vec<Value>, Vec<JoinedRow>)> = vec![];
3607        if grouping {
3608            for r in rows {
3609                let b = bind(&r, ctx);
3610                let mut key = Vec::with_capacity(sel.group_by.len());
3611                for g in &sel.group_by {
3612                    key.push(eval(g, &b)?);
3613                }
3614                match groups.iter_mut().find(|(k, _)| {
3615                    k.len() == key.len()
3616                        && k.iter().zip(&key).all(|(a, b)| {
3617                            // NULLs group TOGETHER, which is what GROUP BY
3618                            // does even though `NULL = NULL` is UNKNOWN.
3619                            (a.is_null() && b.is_null())
3620                                || matches!(cmp_values(a, b), Some(std::cmp::Ordering::Equal))
3621                        })
3622                }) {
3623                    Some((_, bucket)) => bucket.push(r),
3624                    None => groups.push((key, vec![r])),
3625                }
3626            }
3627        } else {
3628            // No GROUP BY: exactly one group, even when it is empty. That is
3629            // what makes `count(*)` answer 0 rather than returning no row.
3630            groups.push((vec![], rows));
3631        }
3632
3633        let n_groups = groups.len();
3634        let mut cols: Vec<OutCol> = vec![];
3635        let mut projected: Vec<(Map<String, Value>, JoinedRow)> = vec![];
3636        let empty: JoinedRow = vec![];
3637
3638        for (gi, (_key, grows)) in groups.iter().enumerate() {
3639            // Every non-aggregate expression left after folding is a GROUP BY
3640            // key, which is constant across the group — so any row of it is
3641            // the right scope, and the folder has already refused anything
3642            // that is not.
3643            let scope = grows.first().unwrap_or(&empty);
3644
3645            if let Some(h) = &sel.having {
3646                let folded = fold_aggregates(h, grows, ctx, &sel.group_by)?;
3647                if truthy(&eval(&folded, &bind(scope, ctx))?) != Some(true) {
3648                    continue;
3649                }
3650            }
3651
3652            let mut obj = Map::new();
3653            for item in &sel.items {
3654                let folded = fold_aggregates(&item.expr, grows, ctx, &sel.group_by)?;
3655                let v = eval(&folded, &bind(scope, ctx))?;
3656                // Output columns are named once, from the first group.
3657                if gi == 0 {
3658                    let name = item.alias.clone().unwrap_or_else(|| derived_name(&item.expr));
3659                    let key = unique_key(&cols, &name);
3660                    obj.insert(key.clone(), v);
3661                    cols.push(OutCol { key, name });
3662                } else {
3663                    let idx = obj.len();
3664                    if let Some(c) = cols.get(idx) {
3665                        obj.insert(c.key.clone(), v);
3666                    }
3667                }
3668            }
3669            projected.push((obj, scope.clone()));
3670        }
3671
3672        plan.notes.push(if grouping {
3673            format!(
3674                "GroupAggregate on {} key(s): {} rows -> {} group(s){}",
3675                sel.group_by.len(),
3676                n_rows_before_group(&plan),
3677                n_groups,
3678                if sel.having.is_some() {
3679                    format!(", HAVING kept {}", projected.len())
3680                } else {
3681                    String::new()
3682                }
3683            )
3684        } else {
3685            format!("Aggregate over {} row(s) -> 1 row", groups[0].1.len())
3686        });
3687        plan.push(Stage::Project { columns: cols.len(), out_rows: projected.len() });
3688        let out = finish(sel, &cols, projected, ctx, &mut plan)?;
3689        return Ok((cols, out, plan));
3690    }
3691
3692    // ── 3. the output shape ─────────────────────────────────────────────────
3693    // Resolved from the ROWS when the select list contains a `*`, because only
3694    // the rows know what columns a schemaless source has. With no rows at all
3695    // a `*` yields no columns, which is the honest answer.
3696    //
3697    // From EVERY row, not the first one. Taking the first row's keys loses any
3698    // field that only later documents carry, and it loses it SILENTLY: two
3699    // documents `{a:1}` and `{a:2, later:"x"}` answered `SELECT *` with one
3700    // column, and `later` — which is right there in the store — simply did not
3701    // appear. A schemaless collection has no row that speaks for the others.
3702    // The translator has always taken the union (`columns_for`), and the
3703    // parity harness is what surfaced the disagreement.
3704    //
3705    // Ordering: the user's own fields in the DOCUMENT'S OWN ORDER, then the
3706    // `_`-prefixed provenance columns sorted. Document order is a deliberate
3707    // property — `serde_json`'s `preserve_order` feature is on crate-wide to
3708    // make it possible — and it is what Postgres does, where `*` follows
3709    // column definition order rather than the alphabet. Putting provenance
3710    // last keeps `_hash` from pushing `status` off the screen.
3711    //
3712    // The first attempt at this sorted the user's fields alphabetically to
3713    // match the TRANSLATOR, which had it backwards: the corpus pins document
3714    // order deliberately, so the translator was the one diverging. It now
3715    // sorts nothing but the provenance block either.
3716    //
3717    // `spans` records which output columns each select ITEM owns, so the
3718    // projection below never has to guess. The previous version walked a
3719    // single counter through both stages, and a `*` that skipped an
3720    // already-named column left the counter pointing at the wrong name — a
3721    // drift that happened to be masked by a fallback.
3722    let mut cols: Vec<OutCol> = vec![];
3723    let mut spans: Vec<(usize, usize)> = Vec::with_capacity(sel.items.len());
3724    for item in &sel.items {
3725        let start = cols.len();
3726        match &item.expr {
3727            Expr::Star => {
3728                let mut plain: Vec<String> = vec![];
3729                let mut meta: Vec<String> = vec![];
3730                for r in &rows {
3731                    for (n, _) in bind(r, ctx).flatten() {
3732                        let target = if n.starts_with('_') { &mut meta } else { &mut plain };
3733                        // A star never emits the same column twice.
3734                        if !target.contains(&n) {
3735                            target.push(n);
3736                        }
3737                    }
3738                }
3739                // Only the provenance block is sorted; the user's fields keep
3740                // the document's order.
3741                meta.sort();
3742                for n in plain.into_iter().chain(meta) {
3743                    if !cols.iter().any(|c| c.name == n) {
3744                        cols.push(OutCol { key: n.clone(), name: n });
3745                    }
3746                }
3747            }
3748            Expr::QualifiedStar(q) => {
3749                if let Some(first) = rows.first() {
3750                    for (n, _) in bind(first, ctx).flatten_binding(q) {
3751                        if !cols.iter().any(|c| c.name == n) {
3752                            cols.push(OutCol { key: n.clone(), name: n });
3753                        }
3754                    }
3755                }
3756            }
3757            _ => {
3758                let name = item.alias.clone().unwrap_or_else(|| derived_name(&item.expr));
3759                // Postgres permits duplicate output names and clients index
3760                // positionally as well as by name, so a collision is NOT
3761                // renamed — silently renaming a column is worse than a
3762                // duplicate, because generated SQL looks for the name it asked
3763                // for. Only the internal KEY is disambiguated.
3764                let key = unique_key(&cols, &name);
3765                cols.push(OutCol { key, name });
3766            }
3767        }
3768        spans.push((start, cols.len()));
3769    }
3770
3771    // ── 4. project ──────────────────────────────────────────────────────────
3772    // The source row is kept beside each projected row, because ORDER BY may
3773    // sort on an expression over columns that are NOT in the select list.
3774    let mut projected: Vec<(Map<String, Value>, JoinedRow)> = Vec::with_capacity(rows.len());
3775    for r in rows {
3776        let b = bind(&r, ctx);
3777        let mut obj = Map::new();
3778        for (i, item) in sel.items.iter().enumerate() {
3779            let (start, end) = spans[i];
3780            match &item.expr {
3781                Expr::Star => {
3782                    for (n, v) in b.flatten() {
3783                        if let Some(c) = cols[start..end].iter().find(|c| c.name == n) {
3784                            obj.entry(c.key.clone()).or_insert(v);
3785                        }
3786                    }
3787                }
3788                Expr::QualifiedStar(q) => {
3789                    for (n, v) in b.flatten_binding(q) {
3790                        if let Some(c) = cols[start..end].iter().find(|c| c.name == n) {
3791                            obj.entry(c.key.clone()).or_insert(v);
3792                        }
3793                    }
3794                }
3795                _ => {
3796                    let v = eval(&item.expr, &b)?;
3797                    if let Some(c) = cols.get(start) {
3798                        obj.insert(c.key.clone(), v);
3799                    }
3800                }
3801            }
3802        }
3803        projected.push((obj, r));
3804    }
3805
3806    plan.push(Stage::Project { columns: cols.len(), out_rows: projected.len() });
3807
3808    // ── 5. DISTINCT ─────────────────────────────────────────────────────────
3809    if sel.distinct {
3810        let in_rows = projected.len();
3811        let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
3812        // Keyed on the PROJECTED values in output order, which is what
3813        // DISTINCT means — not on the source rows.
3814        projected.retain(|(obj, _)| seen.insert(row_key(&cols, obj)));
3815        plan.push(Stage::Distinct { in_rows, out_rows: projected.len() });
3816    }
3817
3818    let out = finish(sel, &cols, projected, ctx, &mut plan)?;
3819    Ok((cols, out, plan))
3820}
3821
3822/// The row count the last stage reported, for the group note. Read back from
3823/// the plan rather than tracked separately, so the number in the note and the
3824/// number in the plan cannot disagree.
3825fn n_rows_before_group(plan: &Plan) -> usize {
3826    plan.stages
3827        .iter()
3828        .rev()
3829        .find_map(|st| match st {
3830            Stage::Filter { out_rows, .. } => Some(*out_rows),
3831            Stage::Join { out_rows, .. } => Some(*out_rows),
3832            Stage::Prefilter { out_rows, .. } => Some(*out_rows),
3833            Stage::Scan { rows, .. } => Some(*rows),
3834            _ => None,
3835        })
3836        .unwrap_or(0)
3837}
3838
3839/// Compare two precomputed sort-key tuples under a sort list.
3840///
3841/// One comparator for the query's `ORDER BY` and for an aggregate's own, so
3842/// `array_agg(x ORDER BY y DESC NULLS LAST)` and
3843/// `SELECT ... ORDER BY y DESC NULLS LAST` cannot order the same values
3844/// differently.
3845fn sort_keys(a: &[Value], b: &[Value], order_by: &[OrderBy]) -> std::cmp::Ordering {
3846    for (i, ob) in order_by.iter().enumerate() {
3847        let (Some(x), Some(y)) = (a.get(i), b.get(i)) else { continue };
3848        let ord = match (x.is_null(), y.is_null()) {
3849            (true, true) => std::cmp::Ordering::Equal,
3850            // NULL placement is a direction-independent choice, so it is
3851            // applied BEFORE the DESC reversal rather than being flipped by it.
3852            (true, false) => {
3853                return if ob.nulls_first {
3854                    std::cmp::Ordering::Less
3855                } else {
3856                    std::cmp::Ordering::Greater
3857                }
3858            }
3859            (false, true) => {
3860                return if ob.nulls_first {
3861                    std::cmp::Ordering::Greater
3862                } else {
3863                    std::cmp::Ordering::Less
3864                }
3865            }
3866            (false, false) => cmp_values(x, y).unwrap_or(std::cmp::Ordering::Equal),
3867        };
3868        let ord = if matches!(ob.dir, Dir::Desc) { ord.reverse() } else { ord };
3869        if !ord.is_eq() {
3870            return ord;
3871        }
3872    }
3873    std::cmp::Ordering::Equal
3874}
3875
3876/// `ORDER BY`, then `OFFSET` / `LIMIT` — the tail every query shape shares.
3877fn finish(
3878    sel: &Select,
3879    cols: &[OutCol],
3880    mut projected: Vec<(Map<String, Value>, JoinedRow)>,
3881    ctx: EvalCtx,
3882    plan: &mut Plan,
3883) -> Result<Vec<Value>> {
3884    // ── 6. ORDER BY ─────────────────────────────────────────────────────────
3885    if !sel.order_by.is_empty() {
3886        // Sort keys are precomputed so the comparator cannot fail halfway
3887        // through a sort — an error raised inside `sort_by` would leave the
3888        // rows in an arbitrary order and still return them.
3889        let mut keyed: Vec<(Vec<Value>, (Map<String, Value>, JoinedRow))> = vec![];
3890        for (obj, src) in projected {
3891            let mut key = vec![];
3892            for ob in &sel.order_by {
3893                let v = match (ob.ordinal, &ob.expr) {
3894                    (Some(n), _) => {
3895                        let c = cols.get(n - 1).ok_or_else(|| {
3896                            anyhow::anyhow!(
3897                                "ORDER BY {} is out of range: the select list has {} \
3898                                 column(s)", n, cols.len())
3899                        })?;
3900                        obj.get(&c.key).cloned().unwrap_or(Value::Null)
3901                    }
3902                    // `ORDER BY "Schema"` — a bare name that is an OUTPUT
3903                    // column sorts by the projected value, as SQL says; psql's
3904                    // `\dP+` orders by its aliases. Only when no output column
3905                    // has the name does it fall through to the source row.
3906                    (None, Some(Expr::Column { qual: None, name }))
3907                        if cols.iter().any(|c| c.name == *name) =>
3908                    {
3909                        let c = cols.iter().find(|c| c.name == *name).expect("checked");
3910                        obj.get(&c.key).cloned().unwrap_or(Value::Null)
3911                    }
3912                    (None, Some(e)) => {
3913                        // An ORDER BY expression may name a column that is not
3914                        // in the select list, so it is evaluated against the
3915                        // SOURCE row.
3916                        eval(e, &bind(&src, ctx))?
3917                    }
3918                    (None, None) => Value::Null,
3919                };
3920                key.push(v);
3921            }
3922            keyed.push((key, (obj, src)));
3923        }
3924
3925        keyed.sort_by(|a, b| sort_keys(&a.0, &b.0, &sel.order_by));
3926
3927        projected = keyed.into_iter().map(|(_, row)| row).collect();
3928        plan.push(Stage::Sort { keys: sel.order_by.len(), rows: projected.len() });
3929    }
3930
3931    // ── 7. OFFSET / LIMIT ───────────────────────────────────────────────────
3932    let mut out: Vec<Value> = projected
3933        .into_iter()
3934        .map(|(obj, _)| Value::Object(obj))
3935        .collect();
3936    let in_rows = out.len();
3937    if let Some(off) = sel.offset {
3938        out = if off >= out.len() { vec![] } else { out.split_off(off) };
3939    }
3940    if let Some(lim) = sel.limit {
3941        out.truncate(lim);
3942    }
3943    if sel.limit.is_some() || sel.offset.is_some() {
3944        plan.push(Stage::Limit {
3945            limit: sel.limit,
3946            offset: sel.offset,
3947            in_rows,
3948            out_rows: out.len(),
3949        });
3950    }
3951
3952    Ok(out)
3953}
3954
3955// ─────────────────────────────────────────────────────────────────────────────
3956// The two join implementations
3957// ─────────────────────────────────────────────────────────────────────────────
3958
3959/// Apply the post-join filter to one produced row.
3960///
3961/// # An `ON` predicate and a post-join `WHERE` predicate are NOT the same thing
3962///
3963/// The physical join evaluates both inside one loop, which is where the
3964/// performance comes from. It does NOT merge them, and the difference is
3965/// semantic law rather than a matter of taste:
3966///
3967/// ```text
3968///   LEFT JOIN ... ON a.x = b.x AND b.tag = 'q'     keeps every left row
3969///   LEFT JOIN ... ON a.x = b.x WHERE b.tag = 'q'   discards the outer rows
3970/// ```
3971///
3972/// So the order is fixed and each step sees only what it should:
3973///
3974/// 1. form the candidate pair
3975/// 2. evaluate `ON` — and this ALONE decides whether the row counts as
3976///    matched, for both the left row and the right row
3977/// 3. synthesise NULLs if the outer join requires it
3978/// 4. evaluate the post-join filter
3979/// 5. count the survivor toward the row budget
3980///
3981/// Step 2 is the load-bearing one. If the filter were allowed to influence
3982/// "matched", a left row whose only partner fails the filter would be
3983/// NULL-extended — and a filter like `WHERE b.tag IS NULL` would then ACCEPT
3984/// that synthesised row, inventing output that the unfused pipeline never
3985/// produces. It is the same trap that made the first predicate-pushdown
3986/// attempt wrong, in a different place.
3987fn keep_row(cand: &JoinedRow, post: Option<&Expr>, removed: &mut usize, ctx: EvalCtx) -> Result<bool> {
3988    let Some(p) = post else { return Ok(true) };
3989    // Only TRUE keeps a row, exactly as a standalone `WHERE` stage does.
3990    if truthy(&eval(p, &bind(cand, ctx))?) == Some(true) {
3991        Ok(true)
3992    } else {
3993        *removed += 1;
3994        Ok(false)
3995    }
3996}
3997
3998/// `RIGHT`/`FULL`: every right row that found no partner survives, with every
3999/// left binding NULL.
4000///
4001/// Shared by both strategies so the two cannot drift apart on the subtlest
4002/// part of outer-join semantics.
4003#[allow(clippy::too_many_arguments)]
4004fn emit_unmatched_right(
4005    out: &mut Vec<JoinedRow>,
4006    kind: JoinKind,
4007    left_bindings: &[String],
4008    right_rows: &[Value],
4009    right_matched: &[bool],
4010    rb: &str,
4011    post: Option<&Expr>,
4012    removed: &mut usize,
4013    ctx: EvalCtx,
4014) -> Result<()> {
4015    if !matches!(kind, JoinKind::Right | JoinKind::Full) {
4016        return Ok(());
4017    }
4018    for (ri, right) in right_rows.iter().enumerate() {
4019        if right_matched[ri] {
4020            continue;
4021        }
4022        let mut cand: JoinedRow = left_bindings.iter().map(|b| (b.clone(), None)).collect();
4023        cand.push((rb.to_string(), Some(right.clone())));
4024        // Outer rows face the post-join filter too — it is a `WHERE`, and a
4025        // `WHERE` applies to every row the join produced.
4026        if keep_row(&cand, post, removed, ctx)? {
4027            out.push(cand);
4028        }
4029    }
4030    Ok(())
4031}
4032
4033/// `LATERAL`: the right side is a subquery re-run for each left row, with
4034/// that row as its scope. INNER and CROSS keep matched pairs; LEFT keeps a
4035/// left row with a NULL right side when the subquery produced nothing.
4036///
4037/// Returns `(rows, removed by the post filter, left rows consumed, right rows
4038/// produced in total)`.
4039fn join_lateral(
4040    left_src: &mut dyn LeftSource,
4041    join: &Join,
4042    resolve: &Resolver,
4043    budget: Option<usize>,
4044    post: Option<&Expr>,
4045    ctx: EvalCtx,
4046) -> Result<(Vec<JoinedRow>, usize, usize, usize)> {
4047    let sub = join
4048        .table
4049        .sub
4050        .as_deref()
4051        .ok_or_else(|| anyhow::anyhow!("LATERAL requires a subquery"))?;
4052    let rb = join.table.binding();
4053    let mut out: Vec<JoinedRow> = vec![];
4054    let mut removed = 0usize;
4055    let mut consumed = 0usize;
4056    let mut produced = 0usize;
4057    while let Some(left) = {
4058        if budget.is_some_and(|b| out.len() >= b) { None } else { left_src.next_left()? }
4059    } {
4060        consumed += 1;
4061        let scope = bind(&left, ctx);
4062        let (cols, rows, _) = execute_inner(sub, resolve, Opts::default(), Some(&scope))?;
4063        produced += rows.len();
4064        let mut matched = false;
4065        for r in rows {
4066            let m = match r {
4067                Value::Object(m) => m,
4068                _ => Map::new(),
4069            };
4070            let mut named = Map::new();
4071            for (i, c) in cols.iter().enumerate() {
4072                let name = join.table.col_aliases.get(i).cloned().unwrap_or_else(|| c.name.clone());
4073                named.entry(name).or_insert(m.get(&c.key).cloned().unwrap_or(Value::Null));
4074            }
4075            let mut cand = left.clone();
4076            cand.push((rb.clone(), Some(Value::Object(named))));
4077            let on_ok = match &join.on {
4078                Some(on) => truthy(&eval(on, &bind(&cand, ctx))?) == Some(true),
4079                None => true,
4080            };
4081            if !on_ok {
4082                continue;
4083            }
4084            matched = true;
4085            if keep_row(&cand, post, &mut removed, ctx)? {
4086                out.push(cand);
4087            }
4088        }
4089        if !matched && matches!(join.kind, JoinKind::Left | JoinKind::Full) {
4090            let mut cand = left.clone();
4091            cand.push((rb.clone(), None));
4092            if keep_row(&cand, post, &mut removed, ctx)? {
4093                out.push(cand);
4094            }
4095        }
4096    }
4097    Ok((out, removed, consumed, produced))
4098}
4099
4100/// The reference strategy: consider every pair.
4101///
4102/// Quadratic, and kept forever anyway. It is the semantic fallback for
4103/// predicates the hash path cannot key on, the implementation of record for
4104/// non-equality joins, and the oracle the differential tests compare against.
4105#[allow(clippy::too_many_arguments)]
4106fn join_nested_loop(
4107    left_src: &mut dyn LeftSource,
4108    left_bindings: &[String],
4109    join: &Join,
4110    right_rows: &[Value],
4111    rb: &str,
4112    budget: Option<usize>,
4113    post: Option<&Expr>,
4114    ctx: EvalCtx,
4115) -> Result<(Vec<JoinedRow>, usize, usize)> {
4116    let mut out: Vec<JoinedRow> = vec![];
4117    let mut removed = 0usize;
4118    // Which right rows found a partner — only needed for RIGHT and FULL.
4119    let mut right_matched = vec![false; right_rows.len()];
4120
4121    let mut consumed = 0usize;
4122    while let Some(left) = {
4123        if budget.is_some_and(|b| out.len() >= b) {
4124            // Stop ASKING. With a streaming left side this is what keeps the
4125            // source from producing rows nobody will look at.
4126            None
4127        } else {
4128            left_src.next_left()?
4129        }
4130    } {
4131        consumed += 1;
4132        let left = &left;
4133        // Decided by the ON clause ALONE. See `keep_row` for why the
4134        // post-join filter must not touch this.
4135        let mut matched = false;
4136        for (ri, right) in right_rows.iter().enumerate() {
4137            let mut cand: JoinedRow = left.clone();
4138            cand.push((rb.to_string(), Some(right.clone())));
4139            let joins_here = match &join.on {
4140                // CROSS JOIN has no predicate: every pair survives.
4141                None => true,
4142                // An ON that evaluates to UNKNOWN does NOT join, exactly
4143                // as in SQL. Treating UNKNOWN as a match would invent
4144                // pairings out of missing data.
4145                Some(on) => truthy(&eval(on, &bind(&cand, ctx))?) == Some(true),
4146            };
4147            if joins_here {
4148                matched = true;
4149                right_matched[ri] = true;
4150                if keep_row(&cand, post, &mut removed, ctx)? {
4151                    out.push(cand);
4152                }
4153            }
4154        }
4155        // LEFT/FULL: an unmatched left row survives with a NULL right.
4156        if !matched && matches!(join.kind, JoinKind::Left | JoinKind::Full) {
4157            let mut cand: JoinedRow = left.clone();
4158            cand.push((rb.to_string(), None));
4159            if keep_row(&cand, post, &mut removed, ctx)? {
4160                out.push(cand);
4161            }
4162        }
4163    }
4164
4165    // Right-outer rows are appended AFTER every left row, so once the budget
4166    // is met they sit beyond the prefix `LIMIT` will keep and cannot affect the
4167    // answer. Skipping them is the point of the budget; emitting them would be
4168    // correct but pointless work.
4169    if !budget.is_some_and(|b| out.len() >= b) {
4170        emit_unmatched_right(
4171            &mut out, join.kind, left_bindings, right_rows, &right_matched, rb, post,
4172            &mut removed, ctx,
4173        )?;
4174    }
4175    Ok((out, removed, consumed))
4176}
4177
4178/// The fast strategy: bucket the right relation, probe it with the left.
4179///
4180/// The hash table is used ONLY to narrow the candidate set. Every surviving
4181/// pair is then evaluated against the complete, unmodified `ON` expression —
4182/// the same call the nested loop makes — so the two strategies answer with the
4183/// same expression evaluated on the same rows. See [`crate::sqljoin`] for why
4184/// bucketing alone would be unsound here.
4185#[allow(clippy::too_many_arguments)]
4186fn join_hash(
4187    left_src: &mut dyn LeftSource,
4188    left_bindings: &[String],
4189    join: &Join,
4190    right_rows: &[Value],
4191    rb: &str,
4192    keys: &[(Expr, Expr)],
4193    budget: Option<usize>,
4194    post: Option<&Expr>,
4195    ctx: EvalCtx,
4196) -> Result<(Vec<JoinedRow>, usize, usize)> {
4197    debug_assert!(!keys.is_empty(), "the planner must not choose Hash with no keys");
4198
4199    // ── build: the right relation, keyed ────────────────────────────────────
4200    let side = sqljoin::HashSide::build(right_rows.len(), |i| {
4201        // A right key reads only the right binding — that is what the planner
4202        // proved — so binding the row alone is sufficient and correct.
4203        let one: JoinedRow = vec![(rb.to_string(), Some(right_rows[i].clone()))];
4204        let b = bind(&one, ctx);
4205        let mut k = Vec::with_capacity(keys.len());
4206        for (_, right_expr) in keys {
4207            match sqljoin::hkey(&eval(right_expr, &b)?) {
4208                Some(h) => k.push(h),
4209                // A NULL anywhere in the key means this row joins nothing.
4210                None => return Ok(None),
4211            }
4212        }
4213        Ok(Some(k))
4214    })?;
4215
4216    // ── probe: the accumulated left rows ────────────────────────────────────
4217    let mut out: Vec<JoinedRow> = vec![];
4218    let mut removed = 0usize;
4219    let mut right_matched = vec![false; right_rows.len()];
4220
4221    let mut consumed = 0usize;
4222    while let Some(left) = {
4223        if budget.is_some_and(|b| out.len() >= b) {
4224            None
4225        } else {
4226            left_src.next_left()?
4227        }
4228    } {
4229        consumed += 1;
4230        let left = &left;
4231        let lb = bind(left, ctx);
4232        let mut lk = Vec::with_capacity(keys.len());
4233        let mut null_key = false;
4234        for (left_expr, _) in keys {
4235            match sqljoin::hkey(&eval(left_expr, &lb)?) {
4236                Some(h) => lk.push(h),
4237                None => {
4238                    null_key = true;
4239                    break;
4240                }
4241            }
4242        }
4243
4244        let mut matched = false;
4245        // A NULL key matches nothing, so the bucket is not consulted. A
4246        // shortcut, not a safeguard: the confirm step below would reject
4247        // those pairs anyway, since `NULL = NULL` is UNKNOWN.
4248        if !null_key {
4249            for &ri in side.probe(&lk) {
4250                let mut cand: JoinedRow = left.clone();
4251                cand.push((rb.to_string(), Some(right_rows[ri].clone())));
4252                // Confirm. The bucket only suggested this pair.
4253                let joins_here = match &join.on {
4254                    None => true,
4255                    Some(on) => truthy(&eval(on, &bind(&cand, ctx))?) == Some(true),
4256                };
4257                if joins_here {
4258                    matched = true;
4259                    right_matched[ri] = true;
4260                    if keep_row(&cand, post, &mut removed, ctx)? {
4261                        out.push(cand);
4262                    }
4263                }
4264            }
4265        }
4266        if !matched && matches!(join.kind, JoinKind::Left | JoinKind::Full) {
4267            let mut cand: JoinedRow = left.clone();
4268            cand.push((rb.to_string(), None));
4269            if keep_row(&cand, post, &mut removed, ctx)? {
4270                out.push(cand);
4271            }
4272        }
4273    }
4274
4275    // See the note in `join_nested_loop`: beyond the budget these rows cannot
4276    // survive the `LIMIT` prefix.
4277    if !budget.is_some_and(|b| out.len() >= b) {
4278        emit_unmatched_right(
4279            &mut out, join.kind, left_bindings, right_rows, &right_matched, rb, post,
4280            &mut removed, ctx,
4281        )?;
4282    }
4283    Ok((out, removed, consumed))
4284}
4285
4286/// Apply the pushed conjuncts for one relation, before it reaches the join.
4287///
4288/// Evaluated against the relation's own binding alone, which is exactly what
4289/// the planner proved is sufficient: a pushed conjunct references only this
4290/// relation, so binding it alone gives the same answer the post-join `WHERE`
4291/// will give for the same row.
4292fn prefilter(
4293    rows: Vec<Value>,
4294    binding: &str,
4295    push: &Pushdown,
4296    plan: &mut Plan,
4297    ctx: EvalCtx,
4298) -> Result<Vec<Value>> {
4299    let Some(preds) = push.for_binding(binding) else { return Ok(rows) };
4300    if preds.is_empty() {
4301        return Ok(rows);
4302    }
4303    let in_rows = rows.len();
4304    let mut kept = Vec::with_capacity(rows.len());
4305    for row in rows {
4306        let one: JoinedRow = vec![(binding.to_string(), Some(row))];
4307        let b = bind(&one, ctx);
4308        let mut keep = true;
4309        for p in preds {
4310            // Only TRUE keeps a row, exactly as in `WHERE`. Treating UNKNOWN
4311            // as a keep would make the pre-filter weaker than the filter it
4312            // duplicates, which is harmless; treating it as a drop when the
4313            // real filter would keep it would not be — so the two must agree,
4314            // and they do because this is the same evaluator call.
4315            if truthy(&eval(p, &b)?) != Some(true) {
4316                keep = false;
4317                break;
4318            }
4319        }
4320        if keep {
4321            // Unwrap the row back out of the single-binding wrapper.
4322            if let Some((_, Some(v))) = one.into_iter().next() {
4323                kept.push(v);
4324            }
4325        }
4326    }
4327    plan.push(Stage::Prefilter {
4328        binding: binding.to_string(),
4329        predicates: preds.len(),
4330        in_rows,
4331        out_rows: kept.len(),
4332    });
4333    Ok(kept)
4334}
4335
4336/// Materialise one FROM item: a named relation through the resolver, a
4337/// derived table by running its query, or a table function by evaluating it.
4338fn fetch(t: &TableRef, resolve: &Resolver, ctx: EvalCtx) -> Result<Box<dyn Relation>> {
4339    // `FROM (SELECT ...) AS t` — the relation IS the subquery's output, keyed
4340    // by output NAME (what the enclosing query addresses), with `AS t(a, b)`
4341    // renaming positionally.
4342    if let Some(sub) = &t.sub {
4343        let (cols, rows, _) = execute_inner(sub, resolve, Opts::default(), ctx.outer)?;
4344        let out = rows
4345            .into_iter()
4346            .map(|r| {
4347                let m = match r {
4348                    Value::Object(m) => m,
4349                    _ => Map::new(),
4350                };
4351                let mut named = Map::new();
4352                for (i, c) in cols.iter().enumerate() {
4353                    let name = t.col_aliases.get(i).cloned().unwrap_or_else(|| c.name.clone());
4354                    // Duplicate output names keep the FIRST, as an unqualified
4355                    // reference to an ambiguous name would resolve to.
4356                    named.entry(name).or_insert(m.get(&c.key).cloned().unwrap_or(Value::Null));
4357                }
4358                Value::Object(named)
4359            })
4360            .collect();
4361        return Ok(from_vec(out));
4362    }
4363
4364    // A table function. Its arguments may read the ENCLOSING row — psql's
4365    // `\dy` writes `unnest(evttags)` over the outer relation's column — so
4366    // they are evaluated in the outer scope.
4367    if let Some(args) = &t.args {
4368        let empty: JoinedRow = vec![];
4369        let scope = bind(&empty, ctx);
4370        let col = |i: usize, default: &str| -> String {
4371            t.col_aliases.get(i).cloned().unwrap_or_else(|| default.to_string())
4372        };
4373        let rows: Vec<Value> = match t.name.as_str() {
4374            "generate_series" => {
4375                let a = num(&eval(args.first().ok_or_else(|| anyhow::anyhow!("generate_series() needs a start"))?, &scope)?);
4376                let b = num(&eval(args.get(1).ok_or_else(|| anyhow::anyhow!("generate_series() needs a stop"))?, &scope)?);
4377                let step = match args.get(2) {
4378                    Some(e) => num(&eval(e, &scope)?).unwrap_or(1.0),
4379                    None => 1.0,
4380                };
4381                match (a, b) {
4382                    // A NULL bound yields no rows, as Postgres answers.
4383                    (Some(a), Some(b)) if step != 0.0 => {
4384                        let mut out = vec![];
4385                        let mut x = a;
4386                        while (step > 0.0 && x <= b) || (step < 0.0 && x >= b) {
4387                            let mut m = Map::new();
4388                            m.insert(col(0, "generate_series"), from_f64(x));
4389                            out.push(Value::Object(m));
4390                            x += step;
4391                            if out.len() > 1_000_000 {
4392                                bail!("generate_series() would produce more than a million rows");
4393                            }
4394                        }
4395                        out
4396                    }
4397                    (Some(_), Some(_)) => bail!("generate_series() step cannot equal zero"),
4398                    _ => vec![],
4399                }
4400            }
4401            "unnest" => match eval(args.first().ok_or_else(|| anyhow::anyhow!("unnest() needs an array"))?, &scope)? {
4402                Value::Array(items) => items
4403                    .into_iter()
4404                    .map(|v| {
4405                        let mut m = Map::new();
4406                        m.insert(col(0, "unnest"), v);
4407                        Value::Object(m)
4408                    })
4409                    .collect(),
4410                // unnest(NULL) is no rows.
4411                _ => vec![],
4412            },
4413            // `generate_subscripts(arr, 1)` — the 1-based positions of an
4414            // array, which is how SQLAlchemy numbers the columns of a primary
4415            // key. An empty or non-array argument yields no rows, as Postgres
4416            // answers.
4417            "generate_subscripts" => {
4418                let dim = match args.get(1) {
4419                    Some(e) => num(&eval(e, &scope)?).unwrap_or(1.0),
4420                    None => 1.0,
4421                };
4422                match eval(args.first().ok_or_else(|| anyhow::anyhow!("generate_subscripts() needs an array"))?, &scope)? {
4423                    // Only one dimension is representable here: a JSON array
4424                    // is a list, not a matrix. Asking for dimension 2 is
4425                    // therefore genuinely empty rather than an error.
4426                    Value::Array(items) if dim == 1.0 => (1..=items.len())
4427                        .map(|i| {
4428                            let mut m = Map::new();
4429                            m.insert(col(0, "generate_subscripts"), from_f64(i as f64));
4430                            Value::Object(m)
4431                        })
4432                        .collect(),
4433                    _ => vec![],
4434                }
4435            }
4436            // NEDB has no partitioning, so a partition tree is empty for every
4437            // relation — the truthful answer, and what lets `\dP+` run.
4438            "pg_partition_tree" | "pg_partition_ancestors" => vec![],
4439            other => bail!(
4440                "the table function {}() is not implemented. It is refused rather \
4441                 than answered with no rows, because an empty relation reads as \
4442                 missing DATA rather than a missing feature",
4443                other
4444            ),
4445        };
4446        return Ok(from_vec(rows));
4447    }
4448
4449    match resolve(&t.name)? {
4450        Some(rel) => Ok(rel),
4451        // Named rather than silently empty: an unknown table that answered
4452        // with no rows would look exactly like an empty one.
4453        None => bail!("relation {:?} does not exist", t.name),
4454    }
4455}
4456
4457/// Where a join reads its LEFT rows from.
4458///
4459/// Both strategies consume the left side in a SINGLE forward pass — the
4460/// nested loop iterates it once, and the hash join probes with it once — so an
4461/// iterator is a natural fit and no rewinding is needed. That is what makes
4462/// the driving relation streamable while the inner side stays materialised.
4463trait LeftSource {
4464    fn next_left(&mut self) -> Result<Option<JoinedRow>>;
4465    /// Best guess at the row count, for the strategy planner.
4466    fn hint(&self) -> Option<usize>;
4467    /// Whatever rows remain, for a query with no join at all.
4468    fn take_rows(&mut self) -> Vec<JoinedRow>;
4469    /// `(pulled, kept)` when this is a streamed base relation.
4470    fn stats(&self) -> Option<(usize, usize)> {
4471        None
4472    }
4473}
4474
4475/// The driving relation, pulled on demand and pre-filtered inline.
4476///
4477/// Pulling lazily is the whole point: with a row budget, a `LIMIT 20` over a
4478/// join stops asking for rows long before the source is exhausted, so the
4479/// source never has to produce the rest.
4480struct StreamLeft<'a> {
4481    rel: Box<dyn Relation>,
4482    binding: String,
4483    preds: Vec<Expr>,
4484    ctx: EvalCtx<'a>,
4485    /// Rows actually requested from the source. Reported as the scan's
4486    /// `actual rows`, which for a streamed relation is the honest number —
4487    /// the total is not merely unknown, it is irrelevant to what happened.
4488    pulled: usize,
4489    kept: usize,
4490}
4491
4492impl<'a> LeftSource for StreamLeft<'a> {
4493    fn next_left(&mut self) -> Result<Option<JoinedRow>> {
4494        while let Some(row) = self.rel.next_row()? {
4495            self.pulled += 1;
4496            let one: JoinedRow = vec![(self.binding.clone(), Some(row))];
4497            if !self.preds.is_empty() {
4498                let b = bind(&one, self.ctx);
4499                let mut keep = true;
4500                for p in &self.preds {
4501                    if truthy(&eval(p, &b)?) != Some(true) {
4502                        keep = false;
4503                        break;
4504                    }
4505                }
4506                if !keep {
4507                    continue;
4508                }
4509            }
4510            self.kept += 1;
4511            return Ok(Some(one));
4512        }
4513        Ok(None)
4514    }
4515    fn hint(&self) -> Option<usize> {
4516        // The source's own count, BEFORE the inline pre-filter. An
4517        // over-estimate, which only ever biases the planner toward the hash
4518        // path — and the two paths are proven equivalent, so a biased choice
4519        // costs time at worst and never correctness.
4520        self.rel.size_hint()
4521    }
4522    fn take_rows(&mut self) -> Vec<JoinedRow> {
4523        // Only reached when there is no join, and the base is materialised in
4524        // that case, so this drains what is left for completeness.
4525        let mut out = vec![];
4526        while let Ok(Some(r)) = self.next_left() {
4527            out.push(r);
4528        }
4529        out
4530    }
4531    fn stats(&self) -> Option<(usize, usize)> {
4532        Some((self.pulled, self.kept))
4533    }
4534}
4535
4536/// An already-materialised left side: the output of a previous join, or a
4537/// base relation in a query the streaming path does not cover.
4538struct VecLeft {
4539    rows: Vec<JoinedRow>,
4540    at: usize,
4541}
4542
4543impl LeftSource for VecLeft {
4544    fn next_left(&mut self) -> Result<Option<JoinedRow>> {
4545        let r = self.rows.get(self.at).cloned();
4546        if r.is_some() {
4547            self.at += 1;
4548        }
4549        Ok(r)
4550    }
4551    fn hint(&self) -> Option<usize> {
4552        Some(self.rows.len().saturating_sub(self.at))
4553    }
4554    fn take_rows(&mut self) -> Vec<JoinedRow> {
4555        let mut v = std::mem::take(&mut self.rows);
4556        if self.at > 0 {
4557            v = v.split_off(self.at);
4558        }
4559        self.at = 0;
4560        v
4561    }
4562}
4563
4564/// Pull a relation completely into memory.
4565///
4566/// Used for the INNER side of a join, which genuinely has to be whole: a hash
4567/// join must build its table before probing, and a nested loop re-scans it for
4568/// every left row. Streaming it would save nothing, so this says plainly that
4569/// it is being materialised on purpose rather than by omission.
4570fn drain(mut rel: Box<dyn Relation>) -> Result<Vec<Value>> {
4571    let mut out = Vec::with_capacity(rel.size_hint().unwrap_or(0));
4572    while let Some(row) = rel.next_row()? {
4573        out.push(row);
4574    }
4575    Ok(out)
4576}
4577
4578/// Parse and run in one call.
4579pub fn run(sql: &str, resolve: &Resolver) -> Result<(Vec<OutCol>, Vec<Value>)> {
4580    let sel = parse(sql)?;
4581    execute(&sel, resolve)
4582}
4583
4584#[cfg(test)]
4585mod lexer_tests {
4586    use super::*;
4587
4588    fn kinds(src: &str) -> Vec<Tok> {
4589        let mut t = lex(src).expect("lexes");
4590        t.pop(); // drop Eof
4591        t
4592    }
4593
4594    #[test]
4595    fn a_word_keeps_both_its_canonical_and_raw_spelling() {
4596        // A column may legitimately be called `count` or `value`; folding case
4597        // in the lexer would later look up a key the data does not have.
4598        assert_eq!(
4599            kinds("Select"),
4600            vec![Tok::Word { upper: "SELECT".into(), raw: "Select".into() }]
4601        );
4602    }
4603
4604    #[test]
4605    fn a_quoted_identifier_is_never_a_keyword() {
4606        assert_eq!(kinds(r#""select""#), vec![Tok::Quoted("select".into())]);
4607        // …and keeps its case, which is the whole point of quoting it.
4608        assert_eq!(kinds(r#""Name""#), vec![Tok::Quoted("Name".into())]);
4609    }
4610
4611    #[test]
4612    fn a_doubled_quote_is_one_literal_quote() {
4613        assert_eq!(kinds("'it''s'"), vec![Tok::Str("it's".into())]);
4614        assert_eq!(kinds(r#""a""b""#), vec![Tok::Quoted("a\"b".into())]);
4615    }
4616
4617    #[test]
4618    fn an_E_string_decodes_the_escapes_catalogue_sql_uses() {
4619        // `array_to_string(d.datacl, E'\n')` appears verbatim in psql's \l.
4620        assert_eq!(kinds(r"E'\n'"), vec![Tok::Str("\n".into())]);
4621        assert_eq!(kinds(r"E'a\tb'"), vec![Tok::Str("a\tb".into())]);
4622        // An unknown escape keeps its character rather than vanishing.
4623        assert_eq!(kinds(r"E'\q'"), vec![Tok::Str("q".into())]);
4624    }
4625
4626    #[test]
4627    fn operators_match_longest_first() {
4628        // Order is load-bearing: `!~*` must not tokenise as `!~` plus `*`.
4629        assert_eq!(kinds("!~*"), vec![Tok::Op("!~*".into())]);
4630        assert_eq!(kinds("!~"), vec![Tok::Op("!~".into())]);
4631        assert_eq!(kinds("~*"), vec![Tok::Op("~*".into())]);
4632        assert_eq!(kinds("<>"), vec![Tok::Op("<>".into())]);
4633        assert_eq!(kinds("!="), vec![Tok::Op("!=".into())]);
4634        assert_eq!(kinds(">="), vec![Tok::Op(">=".into())]);
4635        assert_eq!(kinds("::"), vec![Tok::Op("::".into())]);
4636        assert_eq!(kinds("||"), vec![Tok::Op("||".into())]);
4637        assert_eq!(kinds("~"), vec![Tok::Op("~".into())]);
4638    }
4639
4640    #[test]
4641    fn comments_are_skipped_including_nested_block_comments() {
4642        assert_eq!(kinds("1 -- trailing\n"), vec![Tok::Num(1.0)]);
4643        assert_eq!(kinds("1 /* a */ 2"), vec![Tok::Num(1.0), Tok::Num(2.0)]);
4644        // SQL block comments nest, unlike C's.
4645        assert_eq!(kinds("1 /* a /* b */ c */ 2"), vec![Tok::Num(1.0), Tok::Num(2.0)]);
4646        assert!(lex("1 /* unterminated").is_err());
4647    }
4648
4649    #[test]
4650    fn numbers_parse_including_fractions_and_exponents() {
4651        assert_eq!(kinds("42"), vec![Tok::Num(42.0)]);
4652        assert_eq!(kinds("4.5"), vec![Tok::Num(4.5)]);
4653        assert_eq!(kinds(".5"), vec![Tok::Num(0.5)]);
4654        assert_eq!(kinds("1e3"), vec![Tok::Num(1000.0)]);
4655        assert_eq!(kinds("1e-2"), vec![Tok::Num(0.01)]);
4656        // `1e` is the number 1 followed by an identifier, not a broken number.
4657        assert_eq!(
4658            kinds("1e"),
4659            vec![Tok::Num(1.0), Tok::Word { upper: "E".into(), raw: "e".into() }]
4660        );
4661    }
4662
4663    #[test]
4664    fn an_unterminated_literal_is_an_error_not_a_truncation() {
4665        assert!(lex("'abc").is_err());
4666        assert!(lex(r#""abc"#).is_err());
4667    }
4668
4669    #[test]
4670    fn an_unknown_character_is_REFUSED_rather_than_skipped() {
4671        // Skipping is how a parser silently reads a different query than the
4672        // one it was handed.
4673        let e = lex("SELECT 1 @ 2").unwrap_err().to_string();
4674        assert!(e.contains('@'), "{}", e);
4675    }
4676
4677    #[test]
4678    fn the_real_dn_query_lexes() {
4679        let sql = r#"SELECT n.nspname AS "Name",
4680          pg_catalog.pg_get_userbyid(n.nspowner) AS "Owner"
4681        FROM pg_catalog.pg_namespace n
4682        WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'
4683        ORDER BY 1;"#;
4684        let toks = lex(sql).expect("psql's \\dn must lex");
4685        assert!(toks.contains(&Tok::Quoted("Name".into())));
4686        assert!(toks.contains(&Tok::Op("!~".into())));
4687        assert!(toks.contains(&Tok::Op("<>".into())));
4688        assert!(toks.contains(&Tok::Str("^pg_".into())));
4689    }
4690
4691    #[test]
4692    fn the_real_dt_query_lexes() {
4693        let sql = r#"SELECT n.nspname as "Schema", c.relname as "Name",
4694          CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' END as "Type",
4695          pg_catalog.pg_get_userbyid(c.relowner) as "Owner"
4696        FROM pg_catalog.pg_class c
4697             LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
4698             LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam
4699        WHERE c.relkind IN ('r','p','')
4700              AND n.nspname <> 'pg_catalog'
4701              AND n.nspname !~ '^pg_toast'
4702          AND pg_catalog.pg_table_is_visible(c.oid)
4703        ORDER BY 1,2;"#;
4704        let toks = lex(sql).expect("psql's \\dt must lex");
4705        assert!(toks.iter().any(|t| t.is_kw("CASE")));
4706        assert!(toks.iter().any(|t| t.is_kw("LEFT")));
4707        assert!(toks.iter().any(|t| t.is_kw("JOIN")));
4708        // The empty string in `IN ('r','p','')` must survive as a real value.
4709        assert!(toks.contains(&Tok::Str(String::new())));
4710    }
4711}
4712
4713#[cfg(test)]
4714mod parser_tests {
4715    use super::*;
4716    use serde_json::json;
4717
4718    fn col(qual: Option<&str>, name: &str) -> Expr {
4719        Expr::Column { qual: qual.map(str::to_string), name: name.to_string() }
4720    }
4721
4722    #[test]
4723    fn a_bare_select_list_and_from() {
4724        let s = parse("SELECT a, b FROM t").unwrap();
4725        assert_eq!(s.items.len(), 2);
4726        assert_eq!(s.items[0].expr, col(None, "a"));
4727        assert_eq!(s.from.unwrap().name, "t");
4728    }
4729
4730    /// `AS OF SYSTEM TIME <seq>` hangs on the TABLE, which is what makes the
4731    /// query worth having expressible: one relation in the past joined against
4732    /// another at the tip.
4733    #[test]
4734    fn as_of_system_time_is_read_per_table() {
4735        let s = parse("SELECT total FROM orders AS OF SYSTEM TIME 42").unwrap();
4736        assert_eq!(s.from.clone().unwrap().as_of, Some(42));
4737        assert_eq!(s.from.unwrap().alias, None);
4738
4739        // The alias still follows the temporal qualifier.
4740        let s = parse("SELECT o.total FROM orders AS OF SYSTEM TIME 42 o").unwrap();
4741        let t = s.from.unwrap();
4742        assert_eq!((t.as_of, t.alias.as_deref()), (Some(42), Some("o")));
4743
4744        // ...and `AS <alias>` is still an alias. `AS` starts both, and the
4745        // word after it is the only thing that tells them apart -- without
4746        // that lookahead `parse_table_alias` eats `OF` as the alias and leaves
4747        // `SYSTEM TIME 42` in the token stream.
4748        let t = parse("SELECT x FROM orders AS o").unwrap().from.unwrap();
4749        assert_eq!((t.as_of, t.alias.as_deref()), (None, Some("o")));
4750
4751        // Two relations, one in the past: the shape the per-table placement
4752        // exists for.
4753        let s = parse(
4754            "SELECT o.total, n.total FROM orders AS OF SYSTEM TIME 1 o \
4755             JOIN orders n ON o._id = n._id").unwrap();
4756        assert_eq!(s.from.unwrap().as_of, Some(1));
4757        assert_eq!(s.joins[0].table.as_of, None);
4758
4759        // A WHERE after the qualifier still parses.
4760        let s = parse("SELECT total FROM orders AS OF SYSTEM TIME 7 WHERE _id = '1'").unwrap();
4761        assert_eq!(s.from.unwrap().as_of, Some(7));
4762        assert!(s.where_.is_some());
4763    }
4764
4765    /// NQL's own verbs, said in SQL. This is what folding NQL into neSQL
4766    /// means: the SQL side PARSES them and composes joins and aggregates
4767    /// around them, while the NQL engine stays the one implementation that
4768    /// executes them.
4769    #[test]
4770    fn nqls_verbs_are_table_qualifiers_in_sql() {
4771        let t = parse("SELECT _id FROM orders SEARCH 'acme'").unwrap().from.unwrap();
4772        assert_eq!(t.search.as_deref(), Some("acme"));
4773
4774        let t = parse("SELECT _id FROM orders VALID AS OF '2026-01-01'").unwrap().from.unwrap();
4775        assert_eq!(t.valid_as_of.as_deref(), Some("2026-01-01"));
4776
4777        // All of them at once, in any order the writer chose, plus an alias.
4778        let t = parse(
4779            "SELECT o._id FROM orders AS OF SYSTEM TIME 9 VALID AS OF '2026-01-01' \
4780             SEARCH 'acme' o").unwrap().from.unwrap();
4781        assert_eq!(
4782            (t.as_of, t.valid_as_of.as_deref(), t.search.as_deref(), t.alias.as_deref()),
4783            (Some(9), Some("2026-01-01"), Some("acme"), Some("o")));
4784
4785        // Per-table, which is the point: search one relation, join another.
4786        let s = parse(
4787            "SELECT o._id FROM orders SEARCH 'acme' o JOIN drivers d ON o.driver = d._id")
4788            .unwrap();
4789        assert_eq!(s.from.unwrap().search.as_deref(), Some("acme"));
4790        assert_eq!(s.joins[0].table.search, None);
4791    }
4792
4793    /// The verbs are UNRESERVED, and this is the test that keeps them that way.
4794    ///
4795    /// Adding a keyword to a grammar breaks every query that already used the
4796    /// word as a name. PostgreSQL solves it with `unreserved_keyword`; here the
4797    /// equivalent is a lookahead — `VALID` only starts a clause when `AS OF`
4798    /// follows, `SEARCH` only when a string does — so somebody's collection
4799    /// aliased `search` keeps working after we ship a search verb.
4800    #[test]
4801    fn the_new_verbs_do_not_steal_names_that_already_worked() {
4802        let t = parse("SELECT search.total FROM orders search").unwrap().from.unwrap();
4803        assert_eq!((t.alias.as_deref(), t.search.as_deref()), (Some("search"), None));
4804
4805        let t = parse("SELECT valid.total FROM orders valid").unwrap().from.unwrap();
4806        assert_eq!((t.alias.as_deref(), t.valid_as_of.as_deref()), (Some("valid"), None));
4807
4808        // A COLUMN called `search` or `valid` is untouched either way.
4809        assert!(parse("SELECT search, valid FROM orders").is_ok());
4810        assert!(parse("SELECT _id FROM orders WHERE search = 'x'").is_ok());
4811
4812        // And `AS OF` is still not stolen by the alias path.
4813        let t = parse("SELECT x FROM orders AS valid").unwrap().from.unwrap();
4814        assert_eq!(t.alias.as_deref(), Some("valid"));
4815    }
4816
4817    #[test]
4818    fn a_verbs_argument_is_refused_when_it_is_the_wrong_kind_of_thing() {
4819        // `VALID AS OF 42` is a sequence where a date belongs. System time and
4820        // application-time validity are different questions, so they take
4821        // different arguments and the mistake is named rather than coerced.
4822        let e = parse("SELECT _id FROM orders VALID AS OF 42").unwrap_err().to_string();
4823        assert!(e.contains("date string"), "{}", e);
4824    }
4825
4826    #[test]
4827    fn a_wall_clock_as_of_is_refused_with_the_reason() {
4828        // NEDB's history is sequence-addressed, so a timestamp would be an
4829        // approximation of an exact thing. Same refusal the translator makes,
4830        // in the same words, because a client should not learn two answers.
4831        for sql in [
4832            "SELECT total FROM orders AS OF SYSTEM TIME '2026-01-01'",
4833            "SELECT total FROM orders AS OF SYSTEM TIME now()",
4834            "SELECT total FROM orders AS OF SYSTEM TIME -1",
4835            "SELECT total FROM orders AS OF SYSTEM TIME 1.5",
4836        ] {
4837            let e = parse(sql).unwrap_err().to_string();
4838            assert!(e.contains("sequence number"), "{} -> {}", sql, e);
4839        }
4840    }
4841
4842    #[test]
4843    fn a_clause_keyword_is_never_read_as_a_bare_alias() {
4844        // Without the guard, `FROM t WHERE x = 1` parses `t` aliased as
4845        // `WHERE`, the predicate vanishes, and EVERY row comes back — a
4846        // silently wrong answer of the worst kind.
4847        let s = parse("SELECT a FROM t WHERE a = 1").unwrap();
4848        assert_eq!(s.from.clone().unwrap().alias, None);
4849        assert!(s.where_.is_some(), "the WHERE clause must survive");
4850        let s = parse("SELECT a FROM t ORDER BY a").unwrap();
4851        assert_eq!(s.from.unwrap().alias, None);
4852        assert_eq!(s.order_by.len(), 1);
4853    }
4854
4855    #[test]
4856    fn a_real_alias_is_kept_in_both_spellings() {
4857        assert_eq!(parse("SELECT a FROM t x").unwrap().from.unwrap().alias,
4858                   Some("x".to_string()));
4859        assert_eq!(parse("SELECT a FROM t AS x").unwrap().from.unwrap().alias,
4860                   Some("x".to_string()));
4861    }
4862
4863    #[test]
4864    fn a_tables_binding_is_its_alias_else_its_bare_name() {
4865        let t = TableRef::named("pg_catalog.pg_class", Some("c".into()));
4866        assert_eq!(t.binding(), "c");
4867        let t = TableRef::named("pg_catalog.pg_class", None);
4868        assert_eq!(t.binding(), "pg_class", "the schema is not how a column is addressed");
4869    }
4870
4871    #[test]
4872    fn a_qualified_column_keeps_only_its_immediate_qualifier() {
4873        assert_eq!(parse("SELECT n.nspname FROM x").unwrap().items[0].expr,
4874                   col(Some("n"), "nspname"));
4875        // In `public.orders.id` the binding is `orders`; the schema is not
4876        // part of how a column is addressed.
4877        assert_eq!(parse("SELECT public.orders.id FROM x").unwrap().items[0].expr,
4878                   col(Some("orders"), "id"));
4879    }
4880
4881    #[test]
4882    fn an_alias_may_be_a_quoted_string_with_significant_case() {
4883        let s = parse(r#"SELECT n.nspname AS "Name" FROM x"#).unwrap();
4884        assert_eq!(s.items[0].alias, Some("Name".to_string()));
4885    }
4886
4887    #[test]
4888    fn a_schema_qualified_function_drops_its_schema() {
4889        // `pg_catalog.pg_get_userbyid` is the same function as
4890        // `pg_get_userbyid`; the schema is not part of its identity here.
4891        let s = parse("SELECT pg_catalog.pg_get_userbyid(n.nspowner) FROM x").unwrap();
4892        match &s.items[0].expr {
4893            Expr::Func { name, args } => {
4894                assert_eq!(name, "pg_get_userbyid");
4895                assert_eq!(args.len(), 1);
4896                assert_eq!(args[0], col(Some("n"), "nspowner"));
4897            }
4898            other => panic!("{:?}", other),
4899        }
4900    }
4901
4902    #[test]
4903    fn operator_precedence_matches_sql() {
4904        // AND binds tighter than OR: `a OR b AND c` is `a OR (b AND c)`.
4905        // Getting this backwards silently returns the wrong rows.
4906        let s = parse("SELECT 1 FROM t WHERE a = 1 OR b = 2 AND c = 3").unwrap();
4907        match s.where_.unwrap() {
4908            Expr::Binary { op, right, .. } => {
4909                assert_eq!(op, "OR");
4910                assert!(matches!(*right, Expr::Binary { ref op, .. } if op == "AND"),
4911                        "AND must bind tighter than OR");
4912            }
4913            other => panic!("{:?}", other),
4914        }
4915        // Comparison binds tighter than AND.
4916        let s = parse("SELECT 1 FROM t WHERE a = 1 AND b = 2").unwrap();
4917        assert!(matches!(s.where_.unwrap(), Expr::Binary { ref op, .. } if op == "AND"));
4918        // Multiplication binds tighter than addition.
4919        let s = parse("SELECT 1 + 2 * 3 FROM t").unwrap();
4920        match &s.items[0].expr {
4921            Expr::Binary { op, right, .. } => {
4922                assert_eq!(op, "+");
4923                assert!(matches!(**right, Expr::Binary { ref op, .. } if op == "*"));
4924            }
4925            other => panic!("{:?}", other),
4926        }
4927    }
4928
4929    #[test]
4930    fn parentheses_override_precedence() {
4931        let s = parse("SELECT 1 FROM t WHERE (a = 1 OR b = 2) AND c = 3").unwrap();
4932        match s.where_.unwrap() {
4933            Expr::Binary { op, left, .. } => {
4934                assert_eq!(op, "AND");
4935                assert!(matches!(*left, Expr::Binary { ref op, .. } if op == "OR"));
4936            }
4937            other => panic!("{:?}", other),
4938        }
4939    }
4940
4941    #[test]
4942    fn in_and_is_null_and_between_parse_in_both_polarities() {
4943        let s = parse("SELECT 1 FROM t WHERE k IN ('r','p','')").unwrap();
4944        match s.where_.unwrap() {
4945            Expr::InList { list, negated, .. } => {
4946                assert_eq!(list.len(), 3);
4947                assert!(!negated);
4948                // The empty string in psql's `IN ('r','p','')` is a REAL value.
4949                assert_eq!(list[2], Expr::Literal(json!("")));
4950            }
4951            other => panic!("{:?}", other),
4952        }
4953        assert!(matches!(parse("SELECT 1 FROM t WHERE k NOT IN (1)").unwrap().where_.unwrap(),
4954                         Expr::InList { negated: true, .. }));
4955        assert!(matches!(parse("SELECT 1 FROM t WHERE k IS NULL").unwrap().where_.unwrap(),
4956                         Expr::IsNull { negated: false, .. }));
4957        assert!(matches!(parse("SELECT 1 FROM t WHERE k IS NOT NULL").unwrap().where_.unwrap(),
4958                         Expr::IsNull { negated: true, .. }));
4959        // BETWEEN's bounds must not let AND escape as a boolean operator.
4960        let s = parse("SELECT 1 FROM t WHERE n BETWEEN 1 AND 5").unwrap();
4961        assert!(matches!(s.where_.unwrap(), Expr::Binary { ref op, .. } if op == "AND"));
4962    }
4963
4964    #[test]
4965    fn both_case_spellings_parse() {
4966        // simple CASE — what psql's \dt uses, with nine branches.
4967        let s = parse("SELECT CASE k WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' \
4968                       ELSE 'other' END FROM t").unwrap();
4969        match &s.items[0].expr {
4970            Expr::Case { operand, whens, else_ } => {
4971                assert!(operand.is_some());
4972                assert_eq!(whens.len(), 2);
4973                assert!(else_.is_some());
4974            }
4975            other => panic!("{:?}", other),
4976        }
4977        // searched CASE
4978        let s = parse("SELECT CASE WHEN k = 'r' THEN 1 END FROM t").unwrap();
4979        match &s.items[0].expr {
4980            Expr::Case { operand, whens, else_ } => {
4981                assert!(operand.is_none());
4982                assert_eq!(whens.len(), 1);
4983                assert!(else_.is_none());
4984            }
4985            other => panic!("{:?}", other),
4986        }
4987        // A CASE with no WHEN is malformed and must be refused.
4988        assert!(parse("SELECT CASE k END FROM t").is_err());
4989    }
4990
4991    #[test]
4992    fn every_join_flavour_parses_and_an_inner_join_demands_ON() {
4993        for (sql, kind) in [
4994            ("SELECT 1 FROM a JOIN b ON a.x = b.x", JoinKind::Inner),
4995            ("SELECT 1 FROM a INNER JOIN b ON a.x = b.x", JoinKind::Inner),
4996            ("SELECT 1 FROM a LEFT JOIN b ON a.x = b.x", JoinKind::Left),
4997            ("SELECT 1 FROM a LEFT OUTER JOIN b ON a.x = b.x", JoinKind::Left),
4998            ("SELECT 1 FROM a RIGHT JOIN b ON a.x = b.x", JoinKind::Right),
4999            ("SELECT 1 FROM a FULL OUTER JOIN b ON a.x = b.x", JoinKind::Full),
5000            ("SELECT 1 FROM a CROSS JOIN b", JoinKind::Cross),
5001        ] {
5002            let s = parse(sql).unwrap_or_else(|e| panic!("{}: {}", sql, e));
5003            assert_eq!(s.joins.len(), 1, "{}", sql);
5004            assert_eq!(s.joins[0].kind, kind, "{}", sql);
5005        }
5006        // A comma FROM list is an implicit cross join.
5007        let s = parse("SELECT 1 FROM a, b").unwrap();
5008        assert_eq!(s.joins[0].kind, JoinKind::Cross);
5009        // A join that needs a predicate must not silently become a cross
5010        // product — that turns two tables into n*m confidently wrong rows.
5011        assert!(parse("SELECT 1 FROM a LEFT JOIN b").is_err());
5012        assert!(parse("SELECT 1 FROM a JOIN b USING (x)").is_err());
5013    }
5014
5015    #[test]
5016    fn order_by_reads_a_number_as_an_ORDINAL() {
5017        // psql's \dt ends with `ORDER BY 1,2`. Reading those as the constants
5018        // 1 and 2 sorts every row equally and silently yields an unordered
5019        // listing that looks fine.
5020        let s = parse("SELECT a, b FROM t ORDER BY 1, 2 DESC").unwrap();
5021        assert_eq!(s.order_by.len(), 2);
5022        assert_eq!(s.order_by[0].ordinal, Some(1));
5023        assert_eq!(s.order_by[0].dir, Dir::Asc);
5024        assert_eq!(s.order_by[1].ordinal, Some(2));
5025        assert_eq!(s.order_by[1].dir, Dir::Desc);
5026        // An expression still parses as an expression.
5027        let s = parse("SELECT a FROM t ORDER BY lower(a) ASC").unwrap();
5028        assert!(s.order_by[0].ordinal.is_none());
5029        assert!(s.order_by[0].expr.is_some());
5030    }
5031
5032    #[test]
5033    fn null_ordering_defaults_the_way_postgres_defaults() {
5034        let s = parse("SELECT a FROM t ORDER BY a").unwrap();
5035        assert!(!s.order_by[0].nulls_first, "ASC defaults to NULLS LAST");
5036        let s = parse("SELECT a FROM t ORDER BY a DESC").unwrap();
5037        assert!(s.order_by[0].nulls_first, "DESC defaults to NULLS FIRST");
5038        let s = parse("SELECT a FROM t ORDER BY a NULLS FIRST").unwrap();
5039        assert!(s.order_by[0].nulls_first, "an explicit clause wins");
5040    }
5041
5042    #[test]
5043    fn limit_and_offset_parse_in_either_order() {
5044        let s = parse("SELECT a FROM t LIMIT 5 OFFSET 2").unwrap();
5045        assert_eq!((s.limit, s.offset), (Some(5), Some(2)));
5046        let s = parse("SELECT a FROM t OFFSET 2 LIMIT 5").unwrap();
5047        assert_eq!((s.limit, s.offset), (Some(5), Some(2)));
5048        let s = parse("SELECT a FROM t LIMIT ALL").unwrap();
5049        assert_eq!(s.limit, None);
5050    }
5051
5052    #[test]
5053    fn casts_parse_and_are_recorded_rather_than_rejected() {
5054        // `pr.prattrs::pg_catalog.int2[]` appears verbatim in psql's \d.
5055        let s = parse("SELECT x::int2 FROM t").unwrap();
5056        assert!(matches!(s.items[0].expr, Expr::Cast { .. }));
5057        let s = parse("SELECT x::pg_catalog.int2[] FROM t").unwrap();
5058        match &s.items[0].expr {
5059            Expr::Cast { ty, .. } => assert_eq!(ty, "int2[]"),
5060            other => panic!("{:?}", other),
5061        }
5062    }
5063
5064    #[test]
5065    fn star_and_qualified_star_parse() {
5066        assert_eq!(parse("SELECT * FROM t").unwrap().items[0].expr, Expr::Star);
5067        assert_eq!(parse("SELECT c.* FROM t c").unwrap().items[0].expr,
5068                   Expr::QualifiedStar("c".into()));
5069        // An aggregate is its OWN variant, not a Func whose name happens to
5070        // be in a list — so "is this an aggregate?" is a question about shape.
5071        match &parse("SELECT count(*) FROM t").unwrap().items[0].expr {
5072            Expr::Agg { name, args, order_by, distinct } => {
5073                assert_eq!(name, "count");
5074                assert_eq!(args, &vec![Expr::Star]);
5075                assert!(order_by.is_empty());
5076                assert!(!distinct);
5077            }
5078            other => panic!("{:?}", other),
5079        }
5080        // ...while an ordinary call stays a Func.
5081        assert!(matches!(
5082            &parse("SELECT lower(s) FROM t").unwrap().items[0].expr,
5083            Expr::Func { name, .. } if name == "lower"
5084        ));
5085    }
5086
5087    #[test]
5088    fn an_aggregate_carries_its_own_DISTINCT_and_ORDER_BY() {
5089        // `array_agg(CAST(attname AS TEXT) ORDER BY ord)` is how SQLAlchemy
5090        // reflects a primary key: the array IS the column list and its ORDER
5091        // is the answer, so the ordering cannot be dropped.
5092        match &parse("SELECT array_agg(a.attname ORDER BY a.ord) FROM t a").unwrap().items[0].expr {
5093            Expr::Agg { name, args, order_by, distinct } => {
5094                assert_eq!(name, "array_agg");
5095                assert_eq!(args.len(), 1);
5096                assert_eq!(order_by.len(), 1);
5097                assert!(matches!(order_by[0].dir, Dir::Asc));
5098                assert!(!distinct);
5099            }
5100            other => panic!("{:?}", other),
5101        }
5102        // Direction, NULLS placement and multiple keys all parse the same way
5103        // the query's own ORDER BY does — one shared sort-list parser.
5104        match &parse("SELECT string_agg(DISTINCT s, ',' ORDER BY b DESC NULLS LAST, c) FROM t").unwrap().items[0].expr {
5105            Expr::Agg { name, args, order_by, distinct } => {
5106                assert_eq!(name, "string_agg");
5107                assert_eq!(args.len(), 2, "the separator is an argument, not a sort key");
5108                assert_eq!(order_by.len(), 2);
5109                assert!(matches!(order_by[0].dir, Dir::Desc));
5110                assert!(!order_by[0].nulls_first, "NULLS LAST overrides the DESC default");
5111                assert!(matches!(order_by[1].dir, Dir::Asc));
5112                assert!(distinct);
5113            }
5114            other => panic!("{:?}", other),
5115        }
5116        // DISTINCT is an aggregate-only modifier; on a plain call it is not
5117        // consumed, so the call fails to parse rather than silently dropping it.
5118        assert!(parse("SELECT lower(DISTINCT s) FROM t").is_err());
5119    }
5120
5121    #[test]
5122    fn a_parenthesis_free_function_parses_as_a_zero_arg_call() {
5123        // `current_schema` is legal without parentheses.
5124        match &parse("SELECT current_schema FROM t").unwrap().items[0].expr {
5125            Expr::Func { name, args } => {
5126                assert_eq!(name, "current_schema");
5127                assert!(args.is_empty());
5128            }
5129            other => panic!("{:?}", other),
5130        }
5131    }
5132
5133    #[test]
5134    fn GROUP_BY_and_HAVING_parse_and_what_remains_is_refused_by_name() {
5135        // GROUP BY and HAVING used to be refused here. SQLAlchemy's column
5136        // and index reflection are built on both, so they now parse.
5137        let s = parse("SELECT a, count(*) FROM t GROUP BY a").unwrap();
5138        assert_eq!(s.group_by, vec![Expr::Column { qual: None, name: "a".into() }]);
5139        assert!(s.having.is_none());
5140
5141        let s = parse("SELECT a, b, count(*) FROM t GROUP BY a, b HAVING count(*) > 1").unwrap();
5142        assert_eq!(s.group_by.len(), 2);
5143        assert!(s.having.is_some());
5144
5145        // HAVING is a filter over GROUPS. With neither a GROUP BY nor an
5146        // aggregate there are no groups to filter, and silently treating it
5147        // as a WHERE would answer a different question than the one asked.
5148        let e = parse("SELECT a FROM t HAVING a > 1").unwrap_err().to_string();
5149        assert!(e.contains("HAVING needs a GROUP BY"), "{}", e);
5150
5151        for (sql, needle) in [
5152            ("SELECT DISTINCT ON (a) a FROM t", "DISTINCT ON"),
5153            ("SELECT a, count(*) FROM t GROUP BY ROLLUP (a)", "ROLLUP"),
5154            ("SELECT a, count(*) FROM t GROUP BY CUBE (a)", "CUBE"),
5155        ] {
5156            let e = parse(sql).unwrap_err().to_string();
5157            assert!(e.contains(needle), "{} -> {}", sql, e);
5158        }
5159        // Trailing garbage is an error, not something to ignore.
5160        assert!(parse("SELECT a FROM t JUNK JUNK2").is_err());
5161    }
5162
5163    #[test]
5164    fn THE_dn_QUERY_parses_completely() {
5165        let s = parse(
5166            r#"SELECT n.nspname AS "Name",
5167                 pg_catalog.pg_get_userbyid(n.nspowner) AS "Owner"
5168               FROM pg_catalog.pg_namespace n
5169               WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'
5170               ORDER BY 1;"#,
5171        )
5172        .expect("psql's \\dn must parse");
5173
5174        assert_eq!(s.items.len(), 2);
5175        assert_eq!(s.items[0].alias, Some("Name".into()));
5176        assert_eq!(s.items[1].alias, Some("Owner".into()));
5177        let from = s.from.unwrap();
5178        assert_eq!(from.name, "pg_catalog.pg_namespace");
5179        assert_eq!(from.binding(), "n");
5180        assert!(s.where_.is_some());
5181        assert_eq!(s.order_by[0].ordinal, Some(1));
5182    }
5183
5184    #[test]
5185    fn THE_dt_QUERY_parses_completely() {
5186        let s = parse(
5187            r#"SELECT n.nspname as "Schema",
5188                 c.relname as "Name",
5189                 CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view'
5190                   WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index'
5191                   WHEN 'S' THEN 'sequence' WHEN 't' THEN 'TOAST table'
5192                   WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'partitioned table'
5193                   WHEN 'I' THEN 'partitioned index' END as "Type",
5194                 pg_catalog.pg_get_userbyid(c.relowner) as "Owner"
5195               FROM pg_catalog.pg_class c
5196                    LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
5197                    LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam
5198               WHERE c.relkind IN ('r','p','')
5199                     AND n.nspname <> 'pg_catalog'
5200                     AND n.nspname !~ '^pg_toast'
5201                     AND n.nspname <> 'information_schema'
5202                 AND pg_catalog.pg_table_is_visible(c.oid)
5203               ORDER BY 1,2;"#,
5204        )
5205        .expect("psql's \\dt must parse");
5206
5207        assert_eq!(s.items.len(), 4);
5208        assert_eq!(s.items[2].alias, Some("Type".into()));
5209        match &s.items[2].expr {
5210            Expr::Case { whens, .. } => assert_eq!(whens.len(), 9, "all nine branches"),
5211            other => panic!("{:?}", other),
5212        }
5213        assert_eq!(s.joins.len(), 2);
5214        assert!(s.joins.iter().all(|j| j.kind == JoinKind::Left && j.on.is_some()));
5215        assert_eq!(s.from.unwrap().binding(), "c");
5216        assert_eq!(s.order_by.len(), 2);
5217        assert_eq!(
5218            (s.order_by[0].ordinal, s.order_by[1].ordinal),
5219            (Some(1), Some(2))
5220        );
5221    }
5222}
5223
5224#[cfg(test)]
5225mod eval_tests {
5226    use super::*;
5227    use serde_json::json;
5228
5229    /// One binding named `t` holding `row`.
5230    fn one(row: &Value) -> Bound<'_> {
5231        Bound::new(vec![("t".to_string(), Some(row))])
5232    }
5233
5234    fn ev(sql_expr: &str, row: &Value) -> Result<Value> {
5235        let s = parse(&format!("SELECT {} FROM t", sql_expr))?;
5236        eval(&s.items[0].expr, &one(row))
5237    }
5238
5239    fn v(sql_expr: &str, row: &Value) -> Value {
5240        ev(sql_expr, row).unwrap_or_else(|e| panic!("{}: {}", sql_expr, e))
5241    }
5242
5243    #[test]
5244    fn literals_and_columns_resolve() {
5245        let r = json!({"a": 1, "s": "x", "b": true, "n": null});
5246        assert_eq!(v("42", &r), json!(42));
5247        assert_eq!(v("'hi'", &r), json!("hi"));
5248        assert_eq!(v("NULL", &r), Value::Null);
5249        assert_eq!(v("TRUE", &r), json!(true));
5250        assert_eq!(v("a", &r), json!(1));
5251        assert_eq!(v("t.a", &r), json!(1));
5252        assert_eq!(v("s", &r), json!("x"));
5253        // An absent column is NULL, because a schemaless document may omit
5254        // any field — that is data, not an error.
5255        assert_eq!(v("nosuch", &r), Value::Null);
5256    }
5257
5258    #[test]
5259    fn an_unknown_table_ALIAS_is_an_error_while_an_unknown_column_is_null() {
5260        // The distinction matters: a typo'd alias is a query bug worth
5261        // reporting, while a missing field is ordinary schemaless behaviour.
5262        let r = json!({"a": 1});
5263        assert_eq!(v("t.nosuch", &r), Value::Null);
5264        let e = ev("zz.a", &r).unwrap_err().to_string();
5265        assert!(e.contains("zz"), "{}", e);
5266    }
5267
5268    // ── SQL's three-valued logic. The subtle, dangerous part. ───────────────
5269
5270    #[test]
5271    fn every_comparison_over_NULL_is_UNKNOWN_including_null_equals_null() {
5272        let r = json!({"n": null, "a": 1});
5273        assert_eq!(v("n = 1", &r), Value::Null);
5274        assert_eq!(v("n != 1", &r), Value::Null);
5275        assert_eq!(v("n < 1", &r), Value::Null);
5276        // The one everybody gets wrong: NULL = NULL is UNKNOWN, not true.
5277        assert_eq!(v("n = n", &r), Value::Null);
5278        assert_eq!(v("n = NULL", &r), Value::Null);
5279    }
5280
5281    #[test]
5282    fn NOT_UNKNOWN_is_UNKNOWN_not_true() {
5283        // Collapsing UNKNOWN to false here would make
5284        // `WHERE NOT (n.nspname = 'x')` include the unmatched rows of a LEFT
5285        // JOIN that Postgres excludes — the counts would silently disagree.
5286        let r = json!({"n": null});
5287        assert_eq!(v("NOT (n = 1)", &r), Value::Null);
5288        assert_eq!(v("NOT TRUE", &r), json!(false));
5289        assert_eq!(v("NOT FALSE", &r), json!(true));
5290    }
5291
5292    #[test]
5293    fn AND_and_OR_follow_the_three_valued_truth_tables() {
5294        let r = json!({"n": null});
5295        // false AND unknown = FALSE (the false decides it)
5296        assert_eq!(v("FALSE AND n = 1", &r), json!(false));
5297        // true AND unknown = unknown
5298        assert_eq!(v("TRUE AND n = 1", &r), Value::Null);
5299        // true OR unknown = TRUE (the true decides it)
5300        assert_eq!(v("TRUE OR n = 1", &r), json!(true));
5301        // false OR unknown = unknown
5302        assert_eq!(v("FALSE OR n = 1", &r), Value::Null);
5303        // and the ordinary cases
5304        assert_eq!(v("TRUE AND TRUE", &r), json!(true));
5305        assert_eq!(v("TRUE AND FALSE", &r), json!(false));
5306        assert_eq!(v("FALSE OR FALSE", &r), json!(false));
5307    }
5308
5309    #[test]
5310    fn IS_NULL_is_the_one_predicate_that_is_never_unknown() {
5311        let r = json!({"n": null, "a": 1});
5312        assert_eq!(v("n IS NULL", &r), json!(true));
5313        assert_eq!(v("n IS NOT NULL", &r), json!(false));
5314        assert_eq!(v("a IS NULL", &r), json!(false));
5315        assert_eq!(v("a IS NOT NULL", &r), json!(true));
5316        // An absent column is indistinguishable from an explicit null, which
5317        // is the honest answer for a schemaless store.
5318        assert_eq!(v("nosuch IS NULL", &r), json!(true));
5319    }
5320
5321    #[test]
5322    fn NOT_IN_with_a_NULL_in_the_list_is_UNKNOWN_the_classic_trap() {
5323        let r = json!({"a": 2});
5324        assert_eq!(v("a IN (1, 2)", &r), json!(true));
5325        assert_eq!(v("a IN (1, 3)", &r), json!(false));
5326        assert_eq!(v("a NOT IN (1, 3)", &r), json!(true));
5327        // `2 NOT IN (1, NULL)` is UNKNOWN, not true — 2 MIGHT equal the null.
5328        // Postgres agrees, and getting this wrong silently includes rows.
5329        assert_eq!(v("a NOT IN (1, NULL)", &r), Value::Null);
5330        // A match still decides it even with a null present.
5331        assert_eq!(v("a IN (2, NULL)", &r), json!(true));
5332        // NULL on the left is unknown regardless.
5333        assert_eq!(v("nosuch IN (1)", &r), Value::Null);
5334    }
5335
5336    // ── operators ───────────────────────────────────────────────────────────
5337
5338    #[test]
5339    fn comparisons_work_across_numbers_strings_and_booleans() {
5340        let r = json!({"n": 5, "s": "b", "t": true});
5341        assert_eq!(v("n > 3", &r), json!(true));
5342        assert_eq!(v("n <= 5", &r), json!(true));
5343        assert_eq!(v("s < 'c'", &r), json!(true));
5344        assert_eq!(v("s > 'c'", &r), json!(false));
5345        // A number and a numeric-looking string compare NUMERICALLY, because
5346        // a catalogue oid is a number while a client may quote it.
5347        assert_eq!(v("n = '5'", &r), json!(true));
5348        assert_eq!(v("n = '5.0'", &r), json!(true));
5349        // And a non-numeric string falls back to text comparison rather than
5350        // erroring.
5351        assert_eq!(v("n = 'five'", &r), json!(false));
5352    }
5353
5354    #[test]
5355    fn the_regex_operators_use_the_SAME_matcher_as_NQL() {
5356        // Two implementations would be two chances for the SQL surface and
5357        // the NQL surface to disagree about the same operator.
5358        let r = json!({"s": "pg_catalog"});
5359        assert_eq!(v("s ~ '^pg_'", &r), json!(true));
5360        assert_eq!(v("s !~ '^pg_'", &r), json!(false));
5361        assert_eq!(v("s ~ '^PG_'", &r), json!(false));
5362        assert_eq!(v("s ~* '^PG_'", &r), json!(true));
5363        assert_eq!(v("s !~ '^zz'", &r), json!(true));
5364        // NULL propagates.
5365        assert_eq!(v("nosuch ~ '^x'", &r), Value::Null);
5366        // The ERE subset: groups, alternation, quantifiers — what `\d orders`
5367        // sends (`^(orders)$`) and `\d pg_*` would (`^(pg_.*)$`).
5368        assert_eq!(v("s ~ '^(pg_catalog)$'", &r), json!(true));
5369        assert_eq!(v("s ~ '^(pg_.*)$'", &r), json!(true));
5370        assert_eq!(v("s ~ '^(public|pg_catalog)$'", &r), json!(true));
5371        assert_eq!(v("s ~ '^pg_[a-z]+$'", &r), json!(true));
5372        assert_eq!(v("s ~ '^pg_[0-9]+$'", &r), json!(false));
5373        // And an unsupported construct is refused BY NAME, not approximated.
5374        let e = ev("s ~ 'a{2}'", &r).unwrap_err().to_string();
5375        assert!(e.contains("interval"), "{}", e);
5376    }
5377
5378    #[test]
5379    fn like_works_in_all_four_spellings() {
5380        let r = json!({"s": "Acme Pool"});
5381        assert_eq!(v("s LIKE 'Acme%'", &r), json!(true));
5382        assert_eq!(v("s LIKE 'acme%'", &r), json!(false));
5383        assert_eq!(v("s ILIKE 'acme%'", &r), json!(true));
5384        assert_eq!(v("s NOT LIKE 'zz%'", &r), json!(true));
5385
5386        // ── LIKE ... ESCAPE ─────────────────────────────────────────────────
5387        //
5388        // SQLAlchemy 2.0.53 began emitting this in the catalogue query its
5389        // inspector runs ON CONNECT. 2.0.52 did not. The endpoint went from
5390        // working to `expected ')', got ESCAPE` on a dependency bump nobody
5391        // here made — and because an introspection query is the FIRST thing
5392        // the client sends, it did not degrade a feature, it made the
5393        // connection unusable.
5394        //
5395        // Asserted on SEMANTICS, not just on "it parses": the whole point of
5396        // the clause is that the escaped character stops being a wildcard.
5397        // The row here is {"s": "Acme Pool"} -- a literal space in the middle,
5398        // which is what makes `_` vs escaped `_` an observable difference.
5399        assert_eq!(v(r"s ILIKE 'acme_pool' ESCAPE '/'", &r), json!(true),
5400                   "an unescaped _ is still a wildcard, and matches the space");
5401        assert_eq!(v(r"s ILIKE 'acme/_pool' ESCAPE '/'", &r), json!(false),
5402                   "escaped _ is a LITERAL underscore, which 'Acme Pool' lacks");
5403        assert_eq!(v(r"s ILIKE 'acme%' ESCAPE '/'", &r), json!(true),
5404                   "an unescaped % is still a wildcard");
5405        assert_eq!(v(r"s ILIKE 'acme/%' ESCAPE '/'", &r), json!(false),
5406                   "escaped % is a literal percent sign, not 'anything'");
5407        assert_eq!(v(r"s NOT ILIKE 'acme/_pool' ESCAPE '/'", &r), json!(true),
5408                   "negation composes with ESCAPE");
5409        // A trailing escape char with nothing to escape: PostgreSQL errors,
5410        // but refusing a catalogue query fails the client's CONNECTION, so
5411        // this answers no-match instead.
5412        assert_eq!(v(r"s ILIKE 'acme/' ESCAPE '/'", &r), json!(false),
5413                   "a dangling escape is treated as a literal, never an error");
5414        assert_eq!(v("nosuch LIKE 'x'", &r), Value::Null);
5415    }
5416
5417    #[test]
5418    fn arithmetic_and_concatenation_propagate_null_and_refuse_div_by_zero() {
5419        let r = json!({"a": 7, "b": 2});
5420        assert_eq!(v("a + b", &r), json!(9));
5421        assert_eq!(v("a - b", &r), json!(5));
5422        assert_eq!(v("a * b", &r), json!(14));
5423        assert_eq!(v("a / b", &r), json!(3.5));
5424        assert_eq!(v("a % b", &r), json!(1));
5425        assert_eq!(v("-a", &r), json!(-7));
5426        // A non-integral result stays a float — the rule is about how INTEGRAL
5427        // values render, not about collapsing every number to an integer.
5428        assert_eq!(v("a / b", &r), json!(3.5));
5429        assert_eq!(v("b / a", &r), json!(2.0 / 7.0));
5430        assert_eq!(v("'x' || 'y'", &r), json!("xy"));
5431        assert_eq!(v("'x' || nosuch", &r), Value::Null);
5432        assert_eq!(v("a + nosuch", &r), Value::Null);
5433        // Division by zero is an ERROR in Postgres, not infinity. Returning
5434        // inf would be a confidently wrong number.
5435        assert!(ev("a / 0", &r).is_err());
5436        assert!(ev("a % 0", &r).is_err());
5437    }
5438
5439    #[test]
5440    fn a_cast_is_transparent_rather_than_rejected() {
5441        // `pr.prattrs::pg_catalog.int2[]` appears in real catalogue SQL, and
5442        // the cast cannot change the answer for the shapes it is used on.
5443        let r = json!({"a": 7});
5444        assert_eq!(v("a::int2", &r), json!(7));
5445        assert_eq!(v("a::pg_catalog.int2[]", &r), json!(7));
5446    }
5447
5448    // ── CASE ────────────────────────────────────────────────────────────────
5449
5450    #[test]
5451    fn a_simple_CASE_picks_the_matching_branch() {
5452        // This is psql's \dt shape, with the real relkind values.
5453        let expr = "CASE k WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' \
5454                    WHEN 'i' THEN 'index' END";
5455        assert_eq!(v(expr, &json!({"k": "r"})), json!("table"));
5456        assert_eq!(v(expr, &json!({"k": "v"})), json!("view"));
5457        assert_eq!(v(expr, &json!({"k": "i"})), json!("index"));
5458        // No branch and no ELSE is NULL — which is exactly what \dt relies on
5459        // for a relkind it does not name.
5460        assert_eq!(v(expr, &json!({"k": "z"})), Value::Null);
5461    }
5462
5463    #[test]
5464    fn a_searched_CASE_evaluates_predicates_and_UNKNOWN_does_not_match() {
5465        let expr = "CASE WHEN n > 5 THEN 'big' WHEN n > 0 THEN 'small' ELSE 'none' END";
5466        assert_eq!(v(expr, &json!({"n": 9})), json!("big"));
5467        assert_eq!(v(expr, &json!({"n": 2})), json!("small"));
5468        assert_eq!(v(expr, &json!({"n": -1})), json!("none"));
5469        // An UNKNOWN condition must not match — it falls through to ELSE.
5470        assert_eq!(v(expr, &json!({"other": 1})), json!("none"));
5471    }
5472
5473    #[test]
5474    fn an_ELSE_branch_is_used_when_nothing_matches() {
5475        assert_eq!(
5476            v("CASE k WHEN 'r' THEN 'table' ELSE 'other' END", &json!({"k": "z"})),
5477            json!("other")
5478        );
5479    }
5480
5481    // ── functions ───────────────────────────────────────────────────────────
5482
5483    #[test]
5484    fn the_catalogue_functions_psql_calls_all_answer() {
5485        let r = json!({"o": 10, "enc": 6});
5486        // \dn and \dt both call this for the "Owner" column.
5487        assert_eq!(v("pg_get_userbyid(o)", &r), json!("nedb"));
5488        assert_eq!(v("pg_catalog.pg_get_userbyid(o)", &r), json!("nedb"));
5489        // \dt filters on this. Returning false would hide EVERY table.
5490        assert_eq!(v("pg_table_is_visible(o)", &r), json!(true));
5491        assert_eq!(v("pg_encoding_to_char(enc)", &r), json!("UTF8"));
5492        assert_eq!(v("current_schema", &r), json!("public"));
5493        assert_eq!(v("current_database()", &r), json!("nedb"));
5494        assert_eq!(v("current_user", &r), json!("nedb"));
5495        // The definition-printing functions return NULL rather than invented
5496        // DDL — NEDB has no DDL to print.
5497        assert_eq!(v("pg_get_expr(o, o)", &r), Value::Null);
5498        assert_eq!(v("obj_description(o)", &r), Value::Null);
5499    }
5500
5501    #[test]
5502    fn text_and_null_handling_functions_work() {
5503        let r = json!({"s": "AbC", "n": null});
5504        assert_eq!(v("lower(s)", &r), json!("abc"));
5505        assert_eq!(v("upper(s)", &r), json!("ABC"));
5506        assert_eq!(v("length(s)", &r), json!(3));
5507        assert_eq!(v("lower(n)", &r), Value::Null);
5508        assert_eq!(v("coalesce(n, 'fallback')", &r), json!("fallback"));
5509        assert_eq!(v("coalesce(s, 'fallback')", &r), json!("AbC"));
5510        assert_eq!(v("coalesce(n, n)", &r), Value::Null);
5511        assert_eq!(v("nullif(s, 'AbC')", &r), Value::Null);
5512        assert_eq!(v("nullif(s, 'zz')", &r), json!("AbC"));
5513        // format_type names the type the same way information_schema does.
5514        assert_eq!(v("format_type(20, NULL)", &r), json!("bigint"));
5515    }
5516
5517    #[test]
5518    fn coalesce_does_not_evaluate_past_its_first_non_null() {
5519        // `a / 0` would error; coalesce must never reach it.
5520        let r = json!({"a": 1});
5521        assert_eq!(v("coalesce(a, a / 0)", &r), json!(1));
5522    }
5523
5524    #[test]
5525    fn an_unknown_function_is_REFUSED_rather_than_answered_with_NULL() {
5526        // A NULL column reads as missing DATA rather than a missing feature,
5527        // and somebody would file a data-loss bug against it.
5528        let e = ev("pg_stat_get_numscans(1)", &json!({})).unwrap_err().to_string();
5529        assert!(e.contains("pg_stat_get_numscans"), "{}", e);
5530        assert!(e.contains("refused"), "{}", e);
5531    }
5532
5533    // ── join bindings ───────────────────────────────────────────────────────
5534
5535    #[test]
5536    fn a_qualified_column_reads_only_its_OWN_binding() {
5537        // Both rows have `name`. Without qualifier isolation a join would
5538        // silently read the wrong table's column.
5539        let a = json!({"name": "left", "x": 1});
5540        let b = json!({"name": "right", "y": 2});
5541        let row = Bound::new(vec![("a".into(), Some(&a)), ("b".into(), Some(&b))]);
5542        let get = |e: &str| {
5543            let s = parse(&format!("SELECT {} FROM x", e)).unwrap();
5544            eval(&s.items[0].expr, &row).unwrap()
5545        };
5546        assert_eq!(get("a.name"), json!("left"));
5547        assert_eq!(get("b.name"), json!("right"));
5548        // A bare name takes the first binding that HAS the key.
5549        assert_eq!(get("name"), json!("left"));
5550        assert_eq!(get("y"), json!(2), "a bare name still finds a later binding");
5551    }
5552
5553    #[test]
5554    fn an_unmatched_LEFT_JOIN_side_reads_as_NULL_not_as_a_missing_column() {
5555        // The distinction is what makes `n.nspname IS NULL` answer correctly
5556        // for a row that found no match.
5557        let a = json!({"x": 1});
5558        let row = Bound::new(vec![("a".into(), Some(&a)), ("b".into(), None)]);
5559        let get = |e: &str| {
5560            let s = parse(&format!("SELECT {} FROM x", e)).unwrap();
5561            eval(&s.items[0].expr, &row).unwrap()
5562        };
5563        assert_eq!(get("b.anything"), Value::Null);
5564        assert_eq!(get("b.anything IS NULL"), json!(true));
5565        assert_eq!(get("a.x"), json!(1));
5566    }
5567}
5568
5569#[cfg(test)]
5570mod exec_tests {
5571    use super::*;
5572    use serde_json::json;
5573
5574    /// A resolver over a fixed set of named tables.
5575    fn tables(defs: Vec<(&str, Vec<Value>)>) -> impl Fn(&str) -> Result<Option<Box<dyn Relation>>> {
5576        let owned: Vec<(String, Vec<Value>)> =
5577            defs.into_iter().map(|(n, r)| (n.to_string(), r)).collect();
5578        move |name: &str| {
5579            // Match on the bare name so `pg_catalog.pg_class` finds `pg_class`.
5580            let bare = name.rsplit('.').next().unwrap_or(name);
5581            Ok(owned
5582                .iter()
5583                .find(|(n, _)| n == name || n == bare)
5584                .map(|(_, r)| from_vec(r.clone())))
5585        }
5586    }
5587
5588    fn go(sql: &str, r: &Resolver) -> (Vec<String>, Vec<Value>) {
5589        let (cols, rows) = run(sql, r).unwrap_or_else(|e| panic!("{}\n  -> {}", sql, e));
5590        (cols.into_iter().map(|c| c.name).collect(), rows)
5591    }
5592
5593    fn col(rows: &[Value], name: &str) -> Vec<Value> {
5594        rows.iter().map(|r| r.get(name).cloned().unwrap_or(Value::Null)).collect()
5595    }
5596
5597    // ── the basics, over one table ───────────────────────────────────────────
5598
5599    #[test]
5600    fn select_columns_where_order_limit_offset() {
5601        let t = tables(vec![(
5602            "t",
5603            vec![json!({"a": 3, "s": "c"}), json!({"a": 1, "s": "a"}), json!({"a": 2, "s": "b"})],
5604        )]);
5605        let (names, rows) = go("SELECT a, s FROM t ORDER BY a", &t);
5606        assert_eq!(names, vec!["a", "s"]);
5607        assert_eq!(col(&rows, "a"), vec![json!(1), json!(2), json!(3)]);
5608
5609        let (_, rows) = go("SELECT a FROM t ORDER BY a DESC", &t);
5610        assert_eq!(col(&rows, "a"), vec![json!(3), json!(2), json!(1)]);
5611
5612        let (_, rows) = go("SELECT a FROM t WHERE a > 1 ORDER BY a", &t);
5613        assert_eq!(col(&rows, "a"), vec![json!(2), json!(3)]);
5614
5615        let (_, rows) = go("SELECT a FROM t ORDER BY a LIMIT 2", &t);
5616        assert_eq!(col(&rows, "a"), vec![json!(1), json!(2)]);
5617
5618        let (_, rows) = go("SELECT a FROM t ORDER BY a OFFSET 1", &t);
5619        assert_eq!(col(&rows, "a"), vec![json!(2), json!(3)]);
5620
5621        let (_, rows) = go("SELECT a FROM t ORDER BY a LIMIT 1 OFFSET 1", &t);
5622        assert_eq!(col(&rows, "a"), vec![json!(2)]);
5623
5624        // Past the end is an empty page, not an error.
5625        let (_, rows) = go("SELECT a FROM t OFFSET 99", &t);
5626        assert!(rows.is_empty());
5627    }
5628
5629    #[test]
5630    fn an_output_column_takes_its_alias_or_a_derived_name() {
5631        // Clients index result columns BY NAME, so inventing a different name
5632        // breaks code that works against Postgres.
5633        let t = tables(vec![("t", vec![json!({"a": 1})])]);
5634        assert_eq!(go(r#"SELECT a AS "Name" FROM t"#, &t).0, vec!["Name"]);
5635        assert_eq!(go("SELECT a FROM t", &t).0, vec!["a"]);
5636        assert_eq!(go("SELECT lower('X') FROM t", &t).0, vec!["lower"]);
5637        assert_eq!(go("SELECT 1 + 1 FROM t", &t).0, vec!["?column?"]);
5638        assert_eq!(go("SELECT CASE a WHEN 1 THEN 'x' END FROM t", &t).0, vec!["case"]);
5639    }
5640
5641    #[test]
5642    fn star_expands_from_the_rows_and_a_qualified_star_from_one_binding() {
5643        let t = tables(vec![
5644            ("a", vec![json!({"x": 1, "y": 2})]),
5645            ("b", vec![json!({"z": 3})]),
5646        ]);
5647        let (names, rows) = go("SELECT * FROM a", &t);
5648        assert_eq!(names, vec!["x", "y"]);
5649        assert_eq!(rows.len(), 1);
5650
5651        let (names, _) = go("SELECT a.* FROM a CROSS JOIN b", &t);
5652        assert_eq!(names, vec!["x", "y"], "a qualified star takes ONE binding");
5653
5654        // With no rows a `*` yields no columns, which is the honest answer for
5655        // a schemaless source: only a row knows what columns exist.
5656        let empty = tables(vec![("e", vec![])]);
5657        assert_eq!(go("SELECT * FROM e", &empty).0, Vec::<String>::new());
5658    }
5659
5660    #[test]
5661    fn distinct_dedupes_on_the_projected_values() {
5662        let t = tables(vec![(
5663            "t",
5664            vec![json!({"g": "x"}), json!({"g": "x"}), json!({"g": "y"})],
5665        )]);
5666        let (_, rows) = go("SELECT DISTINCT g FROM t ORDER BY 1", &t);
5667        assert_eq!(col(&rows, "g"), vec![json!("x"), json!("y")]);
5668        let (_, rows) = go("SELECT g FROM t", &t);
5669        assert_eq!(rows.len(), 3, "without DISTINCT every row survives");
5670    }
5671
5672    #[test]
5673    fn order_by_an_ORDINAL_sorts_the_projected_column() {
5674        let t = tables(vec![(
5675            "t",
5676            vec![json!({"a": 2, "b": "z"}), json!({"a": 1, "b": "y"})],
5677        )]);
5678        let (_, rows) = go("SELECT a, b FROM t ORDER BY 1", &t);
5679        assert_eq!(col(&rows, "a"), vec![json!(1), json!(2)]);
5680        let (_, rows) = go("SELECT a, b FROM t ORDER BY 2 DESC", &t);
5681        assert_eq!(col(&rows, "b"), vec![json!("z"), json!("y")]);
5682        // Out of range is an error naming the range, not a silent no-sort.
5683        let e = run("SELECT a FROM t ORDER BY 3", &t).unwrap_err().to_string();
5684        assert!(e.contains("out of range"), "{}", e);
5685    }
5686
5687    #[test]
5688    fn order_by_an_expression_may_use_a_column_NOT_in_the_select_list() {
5689        let t = tables(vec![(
5690            "t",
5691            vec![json!({"a": 1, "hidden": 9}), json!({"a": 2, "hidden": 1})],
5692        )]);
5693        let (_, rows) = go("SELECT a FROM t ORDER BY hidden", &t);
5694        assert_eq!(col(&rows, "a"), vec![json!(2), json!(1)]);
5695    }
5696
5697    #[test]
5698    fn null_ordering_follows_the_direction_defaults() {
5699        let t = tables(vec![(
5700            "t",
5701            vec![json!({"a": 2}), json!({"a": null}), json!({"a": 1})],
5702        )]);
5703        // ASC defaults to NULLS LAST.
5704        assert_eq!(col(&go("SELECT a FROM t ORDER BY a", &t).1, "a"),
5705                   vec![json!(1), json!(2), Value::Null]);
5706        // DESC defaults to NULLS FIRST.
5707        assert_eq!(col(&go("SELECT a FROM t ORDER BY a DESC", &t).1, "a"),
5708                   vec![Value::Null, json!(2), json!(1)]);
5709        // An explicit clause overrides the default.
5710        assert_eq!(col(&go("SELECT a FROM t ORDER BY a NULLS FIRST", &t).1, "a"),
5711                   vec![Value::Null, json!(1), json!(2)]);
5712    }
5713
5714    #[test]
5715    fn a_where_clause_that_is_UNKNOWN_excludes_the_row() {
5716        let t = tables(vec![(
5717            "t",
5718            vec![json!({"a": 1}), json!({"a": null}), json!({"other": 1})],
5719        )]);
5720        // Only the row where the comparison is TRUE survives; UNKNOWN drops.
5721        let (_, rows) = go("SELECT a FROM t WHERE a = 1", &t);
5722        assert_eq!(rows.len(), 1);
5723        // And NOT over UNKNOWN is still UNKNOWN, so it drops too.
5724        let (_, rows) = go("SELECT a FROM t WHERE NOT (a = 1)", &t);
5725        assert_eq!(rows.len(), 0, "NOT UNKNOWN must not resurrect a null row");
5726    }
5727
5728    #[test]
5729    fn select_with_no_FROM_returns_exactly_one_row() {
5730        // A client's liveness probe is written this way.
5731        let t = tables(vec![]);
5732        let (names, rows) = go("SELECT 1", &t);
5733        assert_eq!(rows.len(), 1);
5734        assert_eq!(names, vec!["?column?"]);
5735        assert_eq!(go("SELECT current_schema", &t).1.len(), 1);
5736    }
5737
5738    #[test]
5739    fn an_unknown_relation_is_NAMED_rather_than_answered_with_no_rows() {
5740        // An unknown table that returned zero rows would look exactly like an
5741        // empty one, which is how "where did my data go" starts.
5742        let t = tables(vec![("t", vec![])]);
5743        let e = run("SELECT a FROM nosuchtable", &t).unwrap_err().to_string();
5744        assert!(e.contains("nosuchtable"), "{}", e);
5745        assert!(e.contains("does not exist"), "{}", e);
5746    }
5747
5748    // ── joins ────────────────────────────────────────────────────────────────
5749
5750    #[test]
5751    fn an_inner_join_keeps_only_matching_pairs() {
5752        let t = tables(vec![
5753            ("l", vec![json!({"id": 1, "n": "a"}), json!({"id": 2, "n": "b"})]),
5754            ("r", vec![json!({"lid": 1, "v": "x"})]),
5755        ]);
5756        let (_, rows) = go("SELECT l.n, r.v FROM l JOIN r ON r.lid = l.id", &t);
5757        assert_eq!(rows.len(), 1);
5758        assert_eq!(col(&rows, "n"), vec![json!("a")]);
5759    }
5760
5761    #[test]
5762    fn a_LEFT_join_keeps_unmatched_left_rows_with_NULLs() {
5763        // This is the shape psql's \dt uses twice.
5764        let t = tables(vec![
5765            ("l", vec![json!({"id": 1, "n": "a"}), json!({"id": 2, "n": "b"})]),
5766            ("r", vec![json!({"lid": 1, "v": "x"})]),
5767        ]);
5768        let (_, rows) = go("SELECT l.n, r.v FROM l LEFT JOIN r ON r.lid = l.id ORDER BY 1", &t);
5769        assert_eq!(rows.len(), 2);
5770        assert_eq!(col(&rows, "n"), vec![json!("a"), json!("b")]);
5771        assert_eq!(col(&rows, "v"), vec![json!("x"), Value::Null]);
5772    }
5773
5774    #[test]
5775    fn a_RIGHT_join_keeps_unmatched_right_rows_and_FULL_keeps_both() {
5776        let t = tables(vec![
5777            ("l", vec![json!({"id": 1})]),
5778            ("r", vec![json!({"lid": 1}), json!({"lid": 9})]),
5779        ]);
5780        let (_, rows) = go("SELECT l.id, r.lid FROM l RIGHT JOIN r ON r.lid = l.id", &t);
5781        assert_eq!(rows.len(), 2);
5782        assert!(col(&rows, "id").contains(&Value::Null), "the unmatched right row keeps NULLs on the left");
5783
5784        let t2 = tables(vec![
5785            ("l", vec![json!({"id": 1}), json!({"id": 5})]),
5786            ("r", vec![json!({"lid": 1}), json!({"lid": 9})]),
5787        ]);
5788        let (_, rows) = go("SELECT l.id, r.lid FROM l FULL OUTER JOIN r ON r.lid = l.id", &t2);
5789        assert_eq!(rows.len(), 3, "one match plus one orphan on each side");
5790    }
5791
5792    #[test]
5793    fn a_cross_join_is_the_cartesian_product() {
5794        let t = tables(vec![
5795            ("a", vec![json!({"x": 1}), json!({"x": 2})]),
5796            ("b", vec![json!({"y": 1}), json!({"y": 2}), json!({"y": 3})]),
5797        ]);
5798        assert_eq!(go("SELECT a.x, b.y FROM a CROSS JOIN b", &t).1.len(), 6);
5799        // A comma FROM list means the same thing.
5800        assert_eq!(go("SELECT a.x, b.y FROM a, b", &t).1.len(), 6);
5801    }
5802
5803    #[test]
5804    fn an_ON_clause_that_is_UNKNOWN_does_not_join() {
5805        // Treating UNKNOWN as a match would invent pairings out of missing
5806        // data — rows that exist in neither table.
5807        let t = tables(vec![
5808            ("l", vec![json!({"id": null})]),
5809            ("r", vec![json!({"lid": null})]),
5810        ]);
5811        let (_, rows) = go("SELECT l.id FROM l JOIN r ON r.lid = l.id", &t);
5812        assert!(rows.is_empty(), "NULL = NULL is UNKNOWN, so nothing joins");
5813        // …and on a LEFT JOIN the left row survives with NULLs.
5814        let (_, rows) = go("SELECT l.id FROM l LEFT JOIN r ON r.lid = l.id", &t);
5815        assert_eq!(rows.len(), 1);
5816    }
5817
5818    #[test]
5819    fn two_joins_chain() {
5820        let t = tables(vec![
5821            ("a", vec![json!({"id": 1, "bid": 10, "cid": 100})]),
5822            ("b", vec![json!({"id": 10, "bn": "B"})]),
5823            ("c", vec![json!({"id": 100, "cn": "C"})]),
5824        ]);
5825        let (_, rows) = go(
5826            "SELECT a.id, b.bn, c.cn FROM a \
5827             LEFT JOIN b ON b.id = a.bid \
5828             LEFT JOIN c ON c.id = a.cid",
5829            &t,
5830        );
5831        assert_eq!(rows.len(), 1);
5832        assert_eq!(col(&rows, "bn"), vec![json!("B")]);
5833        assert_eq!(col(&rows, "cn"), vec![json!("C")]);
5834    }
5835
5836    // ── THE acceptance tests ─────────────────────────────────────────────────
5837
5838    /// The catalogue rows psql's `\dn` and `\dt` actually read.
5839    fn catalog() -> impl Fn(&str) -> Result<Option<Box<dyn Relation>>> {
5840        tables(vec![
5841            (
5842                "pg_namespace",
5843                vec![
5844                    json!({"oid": 2200, "nspname": "public", "nspowner": 10}),
5845                    json!({"oid": 11, "nspname": "pg_catalog", "nspowner": 10}),
5846                    json!({"oid": 13000, "nspname": "information_schema", "nspowner": 10}),
5847                ],
5848            ),
5849            (
5850                "pg_class",
5851                vec![
5852                    json!({"oid": 16401, "relname": "orders", "relnamespace": 2200,
5853                           "relkind": "r", "relowner": 10, "relam": 2}),
5854                    json!({"oid": 16402, "relname": "drivers", "relnamespace": 2200,
5855                           "relkind": "r", "relowner": 10, "relam": 2}),
5856                ],
5857            ),
5858            ("pg_am", vec![json!({"oid": 2, "amname": "heap"})]),
5859        ])
5860    }
5861
5862    #[test]
5863    fn THE_dn_QUERY_RUNS_AND_RETURNS_THE_RIGHT_ROWS() {
5864        let (names, rows) = go(
5865            r#"SELECT n.nspname AS "Name",
5866                 pg_catalog.pg_get_userbyid(n.nspowner) AS "Owner"
5867               FROM pg_catalog.pg_namespace n
5868               WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'
5869               ORDER BY 1;"#,
5870            &catalog(),
5871        );
5872
5873        assert_eq!(names, vec!["Name", "Owner"], "psql reads these BY NAME");
5874        // `pg_catalog` is excluded by the regex, `information_schema` by the
5875        // `<>` — leaving exactly the one schema a user cares about.
5876        assert_eq!(col(&rows, "Name"), vec![json!("public")]);
5877        assert_eq!(col(&rows, "Owner"), vec![json!("nedb")]);
5878    }
5879
5880    #[test]
5881    fn THE_dt_QUERY_RUNS_AND_RETURNS_THE_RIGHT_ROWS() {
5882        let (names, rows) = go(
5883            r#"SELECT n.nspname as "Schema",
5884                 c.relname as "Name",
5885                 CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view'
5886                   WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index'
5887                   WHEN 'S' THEN 'sequence' WHEN 't' THEN 'TOAST table'
5888                   WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'partitioned table'
5889                   WHEN 'I' THEN 'partitioned index' END as "Type",
5890                 pg_catalog.pg_get_userbyid(c.relowner) as "Owner"
5891               FROM pg_catalog.pg_class c
5892                    LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
5893                    LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam
5894               WHERE c.relkind IN ('r','p','')
5895                     AND n.nspname <> 'pg_catalog'
5896                     AND n.nspname !~ '^pg_toast'
5897                     AND n.nspname <> 'information_schema'
5898                 AND pg_catalog.pg_table_is_visible(c.oid)
5899               ORDER BY 1,2;"#,
5900            &catalog(),
5901        );
5902
5903        assert_eq!(names, vec!["Schema", "Name", "Type", "Owner"]);
5904        // ORDER BY 1,2 — schema then name, so `drivers` precedes `orders`.
5905        assert_eq!(col(&rows, "Name"), vec![json!("drivers"), json!("orders")]);
5906        assert_eq!(col(&rows, "Schema"), vec![json!("public"), json!("public")]);
5907        // The nine-branch CASE resolves relkind 'r'.
5908        assert_eq!(col(&rows, "Type"), vec![json!("table"), json!("table")]);
5909        assert_eq!(col(&rows, "Owner"), vec![json!("nedb"), json!("nedb")]);
5910    }
5911
5912    #[test]
5913    fn the_dt_query_still_filters_correctly_with_a_system_relation_present() {
5914        // A relation in pg_catalog must be excluded by the `<>`, and one with
5915        // an unlisted relkind by the IN list. If either filter were dropped —
5916        // the bug the parser restructure fixed — \dt would list internals.
5917        let t = tables(vec![
5918            (
5919                "pg_namespace",
5920                vec![
5921                    json!({"oid": 2200, "nspname": "public", "nspowner": 10}),
5922                    json!({"oid": 11, "nspname": "pg_catalog", "nspowner": 10}),
5923                ],
5924            ),
5925            (
5926                "pg_class",
5927                vec![
5928                    json!({"oid": 1, "relname": "mine", "relnamespace": 2200,
5929                           "relkind": "r", "relowner": 10, "relam": 2}),
5930                    json!({"oid": 2, "relname": "pg_internal", "relnamespace": 11,
5931                           "relkind": "r", "relowner": 10, "relam": 2}),
5932                    json!({"oid": 3, "relname": "an_index", "relnamespace": 2200,
5933                           "relkind": "i", "relowner": 10, "relam": 2}),
5934                ],
5935            ),
5936            ("pg_am", vec![json!({"oid": 2, "amname": "heap"})]),
5937        ]);
5938        let (_, rows) = go(
5939            r#"SELECT c.relname as "Name" FROM pg_catalog.pg_class c
5940                 LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
5941               WHERE c.relkind IN ('r','p','') AND n.nspname <> 'pg_catalog'
5942               ORDER BY 1"#,
5943            &t,
5944        );
5945        assert_eq!(col(&rows, "Name"), vec![json!("mine")],
5946                   "a system relation and an index must both be filtered out");
5947    }
5948}
5949
5950#[cfg(test)]
5951mod operator_syntax_tests {
5952    use super::*;
5953    use serde_json::json;
5954
5955    #[test]
5956    fn the_OPERATOR_qualification_psql_generates_is_understood() {
5957        // `\d` writes every operator this way:
5958        //   c.relname OPERATOR(pg_catalog.~) '^(orders)$'
5959        // It names exactly the operator it wraps, so the schema is dropped.
5960        let s = parse(
5961            "SELECT a FROM t WHERE n OPERATOR(pg_catalog.~) '^x' \
5962             AND m OPERATOR(pg_catalog.=) 1",
5963        )
5964        .expect("psql's OPERATOR() form must parse");
5965        match s.where_.unwrap() {
5966            Expr::Binary { op, left, .. } => {
5967                assert_eq!(op, "AND");
5968                assert!(matches!(*left, Expr::Binary { ref op, .. } if op == "~"));
5969            }
5970            other => panic!("{:?}", other),
5971        }
5972    }
5973
5974    #[test]
5975    fn an_OPERATOR_qualified_comparison_EVALUATES() {
5976        let t = |_: &str| -> Result<Option<Box<dyn Relation>>> {
5977            Ok(Some(from_vec(vec![json!({"n": "orders"}), json!({"n": "pg_toast_1"})])))
5978        };
5979        let (_, rows) = run(
5980            "SELECT n FROM pg_class WHERE n OPERATOR(pg_catalog.~) '^ord'",
5981            &t,
5982        )
5983        .unwrap();
5984        assert_eq!(rows.len(), 1);
5985        assert_eq!(rows[0]["n"], json!("orders"));
5986    }
5987
5988    #[test]
5989    fn a_subquery_an_ARRAY_constructor_and_EXISTS_all_PARSE() {
5990        // These used to be refused by name. They are the constructs `\d`,
5991        // `\dp` and `\dT` hinge on, and now each has a variant of its own.
5992        let s = parse("SELECT a FROM t WHERE x = (SELECT 1)").unwrap();
5993        assert!(matches!(s.where_, Some(Expr::Binary { ref right, .. }) if matches!(**right, Expr::Subquery(_))));
5994        let s = parse("SELECT array_to_string(ARRAY(SELECT a FROM b), ',') FROM t").unwrap();
5995        assert!(matches!(&s.items[0].expr, Expr::Func { args, .. } if matches!(args[0], Expr::ArrayQuery(_))));
5996        let s = parse("SELECT a FROM t WHERE EXISTS (SELECT 1)").unwrap();
5997        assert!(matches!(s.where_, Some(Expr::Exists { negated: false, .. })));
5998        let s = parse("SELECT a FROM t WHERE NOT EXISTS (SELECT 1)").unwrap();
5999        assert!(matches!(s.where_, Some(Expr::Unary { ref expr, .. }) if matches!(**expr, Expr::Exists { .. })));
6000        // Quantified comparisons, subscripts, CAST(), IS DISTINCT FROM.
6001        let s = parse("SELECT a FROM t WHERE oid = ANY (polroles) AND 'd' = any(kinds) AND x <> ALL (SELECT y FROM u)").unwrap();
6002        assert!(s.where_.is_some());
6003        let s = parse("SELECT prattrs[s] FROM t").unwrap();
6004        assert!(matches!(s.items[0].expr, Expr::Index { .. }));
6005        let s = parse("SELECT CAST('tuple' AS pg_catalog.text), CAST(n AS int2[]) FROM t").unwrap();
6006        assert!(matches!(&s.items[0].expr, Expr::Cast { ty, .. } if ty == "text"));
6007        assert!(matches!(&s.items[1].expr, Expr::Cast { ty, .. } if ty == "int2[]"));
6008        let s = parse("SELECT a FROM t WHERE a IS DISTINCT FROM b").unwrap();
6009        assert!(matches!(s.where_, Some(Expr::Binary { ref op, .. }) if op == "IS DISTINCT FROM"));
6010        // A comma FROM list interleaved with joins, as `\dF+` writes it.
6011        let s = parse("SELECT 1 FROM c LEFT JOIN n ON n.oid = c.ns, p LEFT JOIN np ON np.oid = p.ns").unwrap();
6012        assert_eq!(s.joins.len(), 3);
6013        assert!(matches!(s.joins[1].kind, JoinKind::Cross));
6014        // LATERAL and a derived table.
6015        let s = parse("SELECT 1 FROM c, LATERAL (SELECT 2 AS two) s").unwrap();
6016        assert!(s.joins[0].table.lateral && s.joins[0].table.sub.is_some());
6017        let s = parse("SELECT tt.a FROM (SELECT 1 AS a UNION ALL SELECT 2) AS tt ORDER BY 1").unwrap();
6018        assert_eq!(s.from.as_ref().unwrap().sub.as_ref().unwrap().set_ops.len(), 1);
6019    }
6020
6021    #[test]
6022    fn a_compound_query_keeps_ORDER_BY_for_the_whole() {
6023        let s = parse("SELECT a FROM t UNION SELECT b FROM u UNION ALL SELECT c FROM v ORDER BY 1 LIMIT 5").unwrap();
6024        assert_eq!(s.set_ops.len(), 2);
6025        assert_eq!(s.set_ops[0].op, SetOp::Union);
6026        assert!(!s.set_ops[0].all);
6027        assert!(s.set_ops[1].all);
6028        assert_eq!(s.order_by.len(), 1);
6029        assert_eq!(s.limit, Some(5));
6030        assert!(s.set_ops[1].query.order_by.is_empty(), "the tail belongs to the whole, not the last arm");
6031    }
6032}
6033
6034#[cfg(test)]
6035mod subquery_exec_tests {
6036    use super::*;
6037    use serde_json::json;
6038
6039    fn tables(defs: Vec<(&str, Vec<Value>)>) -> impl Fn(&str) -> Result<Option<Box<dyn Relation>>> {
6040        let owned: Vec<(String, Vec<Value>)> =
6041            defs.into_iter().map(|(n, r)| (n.to_string(), r)).collect();
6042        move |name: &str| {
6043            let bare = name.rsplit('.').next().unwrap_or(name);
6044            Ok(owned.iter().find(|(n, _)| n == name || n == bare).map(|(_, r)| from_vec(r.clone())))
6045        }
6046    }
6047
6048    fn go(sql: &str, r: &Resolver) -> (Vec<String>, Vec<Value>) {
6049        let (cols, rows) = run(sql, r).unwrap_or_else(|e| panic!("{}\n  -> {}", sql, e));
6050        (cols.into_iter().map(|c| c.name).collect(), rows)
6051    }
6052
6053    fn col(rows: &[Value], name: &str) -> Vec<Value> {
6054        rows.iter().map(|r| r.get(name).cloned().unwrap_or(Value::Null)).collect()
6055    }
6056
6057    fn shop() -> impl Fn(&str) -> Result<Option<Box<dyn Relation>>> {
6058        tables(vec![
6059            ("c", vec![
6060                json!({"id": 1, "name": "ann", "tags": ["a", "b"]}),
6061                json!({"id": 2, "name": "bob", "tags": []}),
6062                json!({"id": 3, "name": "cyd", "tags": null}),
6063            ]),
6064            ("o", vec![
6065                json!({"oid": 10, "cid": 1, "total": 5}),
6066                json!({"oid": 11, "cid": 1, "total": 7}),
6067                json!({"oid": 12, "cid": 2, "total": 9}),
6068            ]),
6069        ])
6070    }
6071
6072    #[test]
6073    fn a_correlated_scalar_subquery_sees_the_outer_row() {
6074        let t = shop();
6075        let (_, rows) = go(
6076            "SELECT c.name, (SELECT sum(o.total) FROM o WHERE o.cid = c.id) AS spent FROM c ORDER BY c.id",
6077            &t,
6078        );
6079        assert_eq!(col(&rows, "spent"), vec![json!(12), json!(9), Value::Null]);
6080        // A scalar subquery returning two rows is an error, as in Postgres.
6081        let e = run("SELECT (SELECT o.total FROM o WHERE o.cid = c.id) FROM c", &t).unwrap_err().to_string();
6082        assert!(e.contains("more than one row"), "{}", e);
6083        // And two columns is an error too.
6084        let e = run("SELECT (SELECT oid, total FROM o) FROM c", &t).unwrap_err().to_string();
6085        assert!(e.contains("exactly one column"), "{}", e);
6086    }
6087
6088    #[test]
6089    fn EXISTS_and_NOT_EXISTS_are_never_unknown() {
6090        let t = shop();
6091        let (_, rows) = go("SELECT c.name FROM c WHERE EXISTS (SELECT 1 FROM o WHERE o.cid = c.id) ORDER BY 1", &t);
6092        assert_eq!(col(&rows, "name"), vec![json!("ann"), json!("bob")]);
6093        let (_, rows) = go("SELECT c.name FROM c WHERE NOT EXISTS (SELECT 1 FROM o WHERE o.cid = c.id)", &t);
6094        assert_eq!(col(&rows, "name"), vec![json!("cyd")]);
6095    }
6096
6097    #[test]
6098    fn ARRAY_of_a_subquery_and_array_to_string_compose_like_psql_dp() {
6099        let t = shop();
6100        let (_, rows) = go(
6101            "SELECT c.name, array_to_string(ARRAY(SELECT o.total FROM o WHERE o.cid = c.id ORDER BY o.total), ',') AS totals FROM c ORDER BY c.id",
6102            &t,
6103        );
6104        // An empty ARRAY joins to the empty string, not NULL — as Postgres.
6105        assert_eq!(col(&rows, "totals"), vec![json!("5,7"), json!("9"), json!("")]);
6106        let (_, rows) = go("SELECT array_length(ARRAY(SELECT oid FROM o), 1) AS n FROM c WHERE c.id = 1", &t);
6107        assert_eq!(col(&rows, "n"), vec![json!(3)]);
6108    }
6109
6110    #[test]
6111    fn ANY_ALL_and_IN_over_arrays_and_subqueries() {
6112        let t = shop();
6113        let (_, rows) = go("SELECT c.name FROM c WHERE 'a' = ANY (c.tags) ORDER BY 1", &t);
6114        assert_eq!(col(&rows, "name"), vec![json!("ann")]);
6115        // ANY over an empty array is false; over NULL is NULL — neither row.
6116        let (_, rows) = go("SELECT c.name FROM c WHERE 'zz' = ANY (c.tags)", &t);
6117        assert!(rows.is_empty());
6118        let (_, rows) = go("SELECT c.name FROM c WHERE c.id = ANY (SELECT o.cid FROM o) ORDER BY 1", &t);
6119        assert_eq!(col(&rows, "name"), vec![json!("ann"), json!("bob")]);
6120        let (_, rows) = go("SELECT c.name FROM c WHERE c.id <> ALL (SELECT o.cid FROM o)", &t);
6121        assert_eq!(col(&rows, "name"), vec![json!("cyd")]);
6122        let (_, rows) = go("SELECT c.name FROM c WHERE c.id IN (SELECT o.cid FROM o WHERE o.total > 6) ORDER BY 1", &t);
6123        assert_eq!(col(&rows, "name"), vec![json!("ann"), json!("bob")]);
6124        let (_, rows) = go("SELECT c.name FROM c WHERE c.id NOT IN (SELECT o.cid FROM o)", &t);
6125        assert_eq!(col(&rows, "name"), vec![json!("cyd")]);
6126        // Subscripts are one-based.
6127        let (_, rows) = go("SELECT c.tags[2] AS second FROM c WHERE c.id = 1", &t);
6128        assert_eq!(col(&rows, "second"), vec![json!("b")]);
6129    }
6130
6131    #[test]
6132    fn set_operations_combine_arms_and_sort_the_whole() {
6133        let t = shop();
6134        let (names, rows) = go("SELECT c.id AS k FROM c UNION ALL SELECT o.cid FROM o ORDER BY 1", &t);
6135        assert_eq!(names, vec!["k"], "column names come from the first arm");
6136        assert_eq!(col(&rows, "k"), vec![json!(1), json!(1), json!(1), json!(2), json!(2), json!(3)]);
6137        let (_, rows) = go("SELECT c.id AS k FROM c UNION SELECT o.cid FROM o ORDER BY 1", &t);
6138        assert_eq!(col(&rows, "k"), vec![json!(1), json!(2), json!(3)]);
6139        let (_, rows) = go("SELECT c.id AS k FROM c INTERSECT SELECT o.cid FROM o ORDER BY 1", &t);
6140        assert_eq!(col(&rows, "k"), vec![json!(1), json!(2)]);
6141        let (_, rows) = go("SELECT c.id AS k FROM c EXCEPT SELECT o.cid FROM o", &t);
6142        assert_eq!(col(&rows, "k"), vec![json!(3)]);
6143        let (_, rows) = go("SELECT c.id AS k FROM c UNION ALL SELECT o.cid FROM o ORDER BY 1 DESC LIMIT 2", &t);
6144        assert_eq!(col(&rows, "k"), vec![json!(3), json!(2)]);
6145        let e = run("SELECT c.id FROM c UNION SELECT o.oid, o.cid FROM o", &t).unwrap_err().to_string();
6146        assert!(e.contains("same number of columns"), "{}", e);
6147    }
6148
6149    #[test]
6150    fn a_derived_table_is_a_relation_and_LATERAL_sees_its_left() {
6151        let t = shop();
6152        let (_, rows) = go(
6153            "SELECT tt.who FROM (SELECT c.name AS who FROM c WHERE c.id < 3) AS tt ORDER BY 1",
6154            &t,
6155        );
6156        assert_eq!(col(&rows, "who"), vec![json!("ann"), json!("bob")]);
6157        // Column aliases rename positionally.
6158        let (_, rows) = go("SELECT tt.x FROM (SELECT c.name FROM c WHERE c.id = 1) AS tt(x)", &t);
6159        assert_eq!(col(&rows, "x"), vec![json!("ann")]);
6160        // LATERAL: one aggregate per left row, then ORDER BY an output alias.
6161        let (_, rows) = go(
6162            "SELECT c.name AS \"Name\", s.n AS \"Orders\" FROM c, LATERAL (SELECT count(*) AS n FROM o WHERE o.cid = c.id) s ORDER BY \"Orders\" DESC, \"Name\"",
6163            &t,
6164        );
6165        assert_eq!(col(&rows, "Name"), vec![json!("ann"), json!("bob"), json!("cyd")]);
6166        assert_eq!(col(&rows, "Orders"), vec![json!(2), json!(1), json!(0)]);
6167    }
6168
6169    #[test]
6170    fn table_functions_generate_series_and_unnest() {
6171        let t = shop();
6172        let (_, rows) = go("SELECT s.generate_series AS n FROM generate_series(1, 3) s", &t);
6173        assert_eq!(col(&rows, "n"), vec![json!(1), json!(2), json!(3)]);
6174        let (_, rows) = go("SELECT x FROM pg_catalog.unnest(ARRAY['p', 'q']) AS t(x)", &t);
6175        assert_eq!(col(&rows, "x"), vec![json!("p"), json!("q")]);
6176        // unnest over the OUTER row's column, as `\dy` writes it.
6177        let (_, rows) = go(
6178            "SELECT c.name, array_to_string(array(select x from pg_catalog.unnest(c.tags) as t(x)), ', ') AS tags FROM c ORDER BY c.id",
6179            &t,
6180        );
6181        assert_eq!(col(&rows, "tags"), vec![json!("a, b"), json!(""), json!("")]);
6182        let e = run("SELECT 1 FROM nosuchfn(1) f", &t).unwrap_err().to_string();
6183        assert!(e.contains("table function nosuchfn()"), "{}", e);
6184    }
6185
6186    #[test]
6187    fn aggregates_without_GROUP_BY_collapse_to_one_row() {
6188        let t = shop();
6189        let (names, rows) = go(
6190            "SELECT count(*), count(c.tags) AS tagged, min(c.name), max(c.name) AS hi, string_agg(c.name, '|') AS all FROM c",
6191            &t,
6192        );
6193        assert_eq!(names, vec!["count", "tagged", "min", "hi", "all"]);
6194        assert_eq!(rows.len(), 1);
6195        assert_eq!(rows[0]["count"], json!(3));
6196        assert_eq!(rows[0]["tagged"], json!(2), "count(x) skips NULL");
6197        assert_eq!(rows[0]["min"], json!("ann"));
6198        assert_eq!(rows[0]["hi"], json!("cyd"));
6199        assert_eq!(rows[0]["all"], json!("ann|bob|cyd"));
6200        // Over no rows: count is 0, everything else NULL.
6201        let (_, rows) = go("SELECT count(*) AS n, sum(o.total) AS s FROM o WHERE o.total > 100", &t);
6202        assert_eq!(rows[0]["n"], json!(0));
6203        assert_eq!(rows[0]["s"], Value::Null);
6204        // Arithmetic around an aggregate works; a bare column beside one is
6205        // refused with Postgres's own message.
6206        let (_, rows) = go("SELECT sum(o.total) / count(*) AS avg_total, avg(o.total) AS a FROM o", &t);
6207        assert_eq!(rows[0]["avg_total"], json!(7));
6208        assert_eq!(rows[0]["a"], json!(7));
6209        let e = run("SELECT c.name, count(*) FROM c", &t).unwrap_err().to_string();
6210        assert!(e.contains("must appear in the GROUP BY clause"), "{}", e);
6211    }
6212
6213    #[test]
6214    fn IS_DISTINCT_FROM_is_null_safe() {
6215        let t = shop();
6216        let (_, rows) = go("SELECT c.name FROM c WHERE c.tags IS DISTINCT FROM NULL ORDER BY 1", &t);
6217        assert_eq!(col(&rows, "name"), vec![json!("ann"), json!("bob")]);
6218        let (_, rows) = go("SELECT c.name FROM c WHERE c.tags IS NOT DISTINCT FROM NULL", &t);
6219        assert_eq!(col(&rows, "name"), vec![json!("cyd")]);
6220    }
6221
6222    #[test]
6223    fn THE_dT_QUERY_RUNS_over_a_catalogue_fixture() {
6224        // psql 17's \dT, verbatim: two correlated subqueries and NOT EXISTS.
6225        let t = tables(vec![
6226            ("pg_namespace", vec![
6227                json!({"oid": 11, "nspname": "pg_catalog"}),
6228                json!({"oid": 2200, "nspname": "public"}),
6229            ]),
6230            ("pg_type", vec![
6231                json!({"oid": 25, "typname": "text", "typnamespace": 11, "typrelid": 0, "typelem": 0, "typarray": 1009}),
6232                json!({"oid": 1009, "typname": "_text", "typnamespace": 11, "typrelid": 0, "typelem": 25, "typarray": 0}),
6233                json!({"oid": 70000, "typname": "mood", "typnamespace": 2200, "typrelid": 0, "typelem": 0, "typarray": 70001}),
6234                json!({"oid": 70001, "typname": "_mood", "typnamespace": 2200, "typrelid": 0, "typelem": 70000, "typarray": 0}),
6235            ]),
6236            ("pg_class", vec![]),
6237        ]);
6238        let (_, rows) = go(
6239            r#"SELECT n.nspname as "Schema",
6240                 pg_catalog.format_type(t.oid, NULL) AS "Name",
6241                 pg_catalog.obj_description(t.oid, 'pg_type') as "Description"
6242               FROM pg_catalog.pg_type t
6243                    LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
6244               WHERE (t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid))
6245                 AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid)
6246                 AND n.nspname <> 'pg_catalog'
6247                 AND n.nspname <> 'information_schema'
6248                 AND pg_catalog.pg_type_is_visible(t.oid)
6249               ORDER BY 1, 2;"#,
6250            &t,
6251        );
6252        // `_mood` is hidden by NOT EXISTS (its element type's typarray is
6253        // it), `text` and `_text` by the schema filter — `mood` remains.
6254        assert_eq!(rows.len(), 1, "{:?}", rows);
6255        assert_eq!(col(&rows, "Schema"), vec![json!("public")]);
6256    }
6257}
6258
6259#[cfg(test)]
6260mod collate_tests {
6261    use super::*;
6262    use serde_json::json;
6263
6264    #[test]
6265    fn COLLATE_is_consumed_because_it_cannot_change_the_answer() {
6266        // psql writes `COLLATE pg_catalog."C"` throughout `\d`. NEDB has one
6267        // collation, so refusing a clause that provably has no effect would
6268        // reject a query whose result is already correct.
6269        for sql in [
6270            r#"SELECT a FROM t ORDER BY a COLLATE "C""#,
6271            r#"SELECT a COLLATE "C" FROM t"#,
6272            r#"SELECT a FROM t WHERE a COLLATE pg_catalog."C" = 'x'"#,
6273        ] {
6274            parse(sql).unwrap_or_else(|e| panic!("{} -> {}", sql, e));
6275        }
6276        // A malformed COLLATE is still an error rather than silently skipped.
6277        assert!(parse("SELECT a FROM t ORDER BY a COLLATE").is_err());
6278    }
6279
6280    #[test]
6281    fn a_COLLATE_annotated_comparison_still_evaluates() {
6282        let t = |_: &str| -> Result<Option<Box<dyn Relation>>> {
6283            Ok(Some(from_vec(vec![json!({"n": "b"}), json!({"n": "a"})])))
6284        };
6285        let (_, rows) = run(r#"SELECT n FROM pg_class ORDER BY n COLLATE "C""#, &t).unwrap();
6286        assert_eq!(rows[0]["n"], json!("a"), "the ORDER BY still sorts");
6287    }
6288}