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