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}
327
328#[derive(Debug, Clone, PartialEq)]
329pub struct SelectItem {
330    pub expr: Expr,
331    /// The name the client sees. `None` means it is derived from the
332    /// expression, the way Postgres derives it.
333    pub alias: Option<String>,
334}
335
336#[derive(Debug, Clone, Copy, PartialEq, Eq)]
337pub enum JoinKind { Inner, Left, Right, Full, Cross }
338
339#[derive(Debug, Clone, PartialEq)]
340pub struct TableRef {
341    /// The table name as written, minus quoting. A `pg_catalog.` qualifier is
342    /// preserved here and resolved by the caller, because `information_schema`
343    /// table names collide with plausible user collection names.
344    pub name: String,
345    pub alias: Option<String>,
346}
347
348impl TableRef {
349    /// How this table's columns are addressed: the alias when given, else the
350    /// table's own bare name, which is what SQL says.
351    pub fn binding(&self) -> String {
352        self.alias.clone().unwrap_or_else(|| {
353            self.name.rsplit('.').next().unwrap_or(&self.name).to_string()
354        })
355    }
356}
357
358#[derive(Debug, Clone, PartialEq)]
359pub struct Join {
360    pub kind: JoinKind,
361    pub table: TableRef,
362    pub on: Option<Expr>,
363}
364
365#[derive(Debug, Clone, Copy, PartialEq)]
366pub enum Dir { Asc, Desc }
367
368#[derive(Debug, Clone, PartialEq)]
369pub struct OrderBy {
370    /// `ORDER BY 1` is an ORDINAL into the select list, not the number 1.
371    /// psql's `\dt` ends with `ORDER BY 1,2`, so reading it as a constant
372    /// would silently produce an unordered listing.
373    pub ordinal: Option<usize>,
374    pub expr: Option<Expr>,
375    pub dir: Dir,
376    /// Postgres defaults NULLS LAST for ASC and NULLS FIRST for DESC.
377    pub nulls_first: bool,
378}
379
380#[derive(Debug, Clone, PartialEq)]
381pub struct Select {
382    pub distinct: bool,
383    pub items: Vec<SelectItem>,
384    pub from: Option<TableRef>,
385    pub joins: Vec<Join>,
386    pub where_: Option<Expr>,
387    pub order_by: Vec<OrderBy>,
388    pub limit: Option<usize>,
389    pub offset: Option<usize>,
390}
391
392// ─────────────────────────────────────────────────────────────────────────────
393// Phase 2b — the parser
394// ─────────────────────────────────────────────────────────────────────────────
395
396/// Binding power for a binary operator. Higher binds tighter.
397///
398/// Written as a table rather than as nested recursive-descent functions so the
399/// precedence is READABLE and auditable in one place — a hand-rolled cascade
400/// is where operator precedence bugs hide, and a precedence bug in a WHERE
401/// clause silently returns the wrong rows.
402fn binding_power(op: &str) -> Option<u8> {
403    Some(match op {
404        "OR" => 1,
405        "AND" => 2,
406        // Comparison and pattern matching sit at the same level, and are
407        // non-associative in Postgres. Left association here is harmless
408        // because chaining them is a type error anyway.
409        "=" | "!=" | "<>" | "<" | "<=" | ">" | ">=" | "~" | "~*" | "!~" | "!~*"
410        | "LIKE" | "ILIKE" | "NOT LIKE" | "NOT ILIKE" => 4,
411        "||" => 5,
412        "+" | "-" => 6,
413        "*" | "/" | "%" => 7,
414        _ => return None,
415    })
416}
417
418struct Parser {
419    toks: Vec<Tok>,
420    pos: usize,
421}
422
423impl Parser {
424    fn peek(&self) -> &Tok {
425        self.toks.get(self.pos).unwrap_or(&Tok::Eof)
426    }
427    fn peek_at(&self, n: usize) -> &Tok {
428        self.toks.get(self.pos + n).unwrap_or(&Tok::Eof)
429    }
430    fn next(&mut self) -> Tok {
431        let t = self.peek().clone();
432        self.pos += 1;
433        t
434    }
435    fn eat_kw(&mut self, kw: &str) -> bool {
436        if self.peek().is_kw(kw) {
437            self.pos += 1;
438            true
439        } else {
440            false
441        }
442    }
443    fn expect_kw(&mut self, kw: &str) -> Result<()> {
444        if self.eat_kw(kw) {
445            Ok(())
446        } else {
447            bail!("expected {} , got {:?}", kw, self.peek())
448        }
449    }
450    fn eat_punct(&mut self, c: char) -> bool {
451        if matches!(self.peek(), Tok::Punct(p) if *p == c) {
452            self.pos += 1;
453            true
454        } else {
455            false
456        }
457    }
458    fn expect_punct(&mut self, c: char) -> Result<()> {
459        if self.eat_punct(c) {
460            Ok(())
461        } else {
462            bail!("expected {:?}, got {:?}", c, self.peek())
463        }
464    }
465    fn eat_op(&mut self, op: &str) -> bool {
466        if matches!(self.peek(), Tok::Op(o) if o == op) {
467            self.pos += 1;
468            true
469        } else {
470            false
471        }
472    }
473
474    // ── expressions ─────────────────────────────────────────────────────────
475
476    fn parse_expr(&mut self) -> Result<Expr> {
477        self.parse_bin(0)
478    }
479
480    /// Precedence climbing. One loop, one table, no cascade of near-identical
481    /// functions to keep in sync.
482    fn parse_bin(&mut self, min_bp: u8) -> Result<Expr> {
483        let mut left = self.parse_unary()?;
484
485        loop {
486            // A word operator (AND / OR / LIKE / NOT LIKE) and a symbol
487            // operator are both binary here; normalise to one string.
488            // `OPERATOR(pg_catalog.~)` — Postgres's explicit operator
489            // qualification, which psql generates throughout `\d`. It names
490            // exactly the operator it wraps, so the schema is dropped and the
491            // symbol is used directly.
492            if self.peek().is_kw("OPERATOR") && matches!(self.peek_at(1), Tok::Punct('(')) {
493                let save = self.pos;
494                self.pos += 2;
495                // Skip any `schema.` qualification before the symbol.
496                let mut sym = None;
497                while sym.is_none() {
498                    match self.next() {
499                        Tok::Op(o) => sym = Some(o),
500                        Tok::Word { .. } | Tok::Punct('.') => continue,
501                        _ => break,
502                    }
503                }
504                match sym {
505                    Some(o) if binding_power(&o).is_some() && self.eat_punct(')') => {
506                        let bp = binding_power(&o).unwrap();
507                        if bp < min_bp {
508                            self.pos = save;
509                            break;
510                        }
511                        let right = self.parse_bin(bp + 1)?;
512                        left = Expr::Binary {
513                            op: o,
514                            left: Box::new(left),
515                            right: Box::new(right),
516                        };
517                        continue;
518                    }
519                    // Not an operator we know: rewind so the caller reports
520                    // the real position rather than a half-consumed clause.
521                    _ => {
522                        self.pos = save;
523                        break;
524                    }
525                }
526            }
527
528            let (op, width) = match self.peek() {
529                Tok::Op(o) if binding_power(o).is_some() => (o.clone(), 1usize),
530                Tok::Word { upper, .. } if upper == "AND" || upper == "OR" => (upper.clone(), 1),
531                Tok::Word { upper, .. } if upper == "LIKE" || upper == "ILIKE" => (upper.clone(), 1),
532                Tok::Word { upper, .. } if upper == "NOT" => {
533                    // `NOT LIKE` / `NOT ILIKE` / `NOT IN` / `NOT BETWEEN`.
534                    match self.peek_at(1) {
535                        Tok::Word { upper: u2, .. } if u2 == "LIKE" || u2 == "ILIKE" => {
536                            (format!("NOT {}", u2), 2)
537                        }
538                        _ => break,
539                    }
540                }
541                _ => break,
542            };
543
544            let bp = match binding_power(&op) {
545                Some(bp) if bp >= min_bp => bp,
546                _ => break,
547            };
548            self.pos += width;
549            // Left-associative: the right side binds tighter than this level.
550            let right = self.parse_bin(bp + 1)?;
551            left = Expr::Binary { op, left: Box::new(left), right: Box::new(right) };
552        }
553
554        Ok(left)
555    }
556
557    fn parse_postfix(&mut self, mut e: Expr) -> Result<Expr> {
558        loop {
559            // IS [NOT] NULL
560            if self.peek().is_kw("IS") {
561                self.pos += 1;
562                let negated = self.eat_kw("NOT");
563                if !self.eat_kw("NULL") {
564                    // `IS TRUE` / `IS FALSE` are the other legal spellings.
565                    if self.eat_kw("TRUE") {
566                        e = Expr::Binary {
567                            op: "=".into(),
568                            left: Box::new(e),
569                            right: Box::new(Expr::Literal(Value::Bool(!negated))),
570                        };
571                        continue;
572                    }
573                    if self.eat_kw("FALSE") {
574                        e = Expr::Binary {
575                            op: "=".into(),
576                            left: Box::new(e),
577                            right: Box::new(Expr::Literal(Value::Bool(negated))),
578                        };
579                        continue;
580                    }
581                    bail!("expected NULL, TRUE or FALSE after IS, got {:?}", self.peek());
582                }
583                e = Expr::IsNull { expr: Box::new(e), negated };
584                continue;
585            }
586
587            // [NOT] IN (...)
588            let negated_in = if self.peek().is_kw("NOT") && self.peek_at(1).is_kw("IN") {
589                self.pos += 2;
590                true
591            } else if self.peek().is_kw("IN") {
592                self.pos += 1;
593                false
594            } else {
595                // [NOT] BETWEEN a AND b
596                let negated_between =
597                    if self.peek().is_kw("NOT") && self.peek_at(1).is_kw("BETWEEN") {
598                        self.pos += 2;
599                        true
600                    } else if self.peek().is_kw("BETWEEN") {
601                        self.pos += 1;
602                        false
603                    } else {
604                        break;
605                    };
606                // BETWEEN's bounds bind tighter than AND, so the bounds are
607                // parsed at a level above AND — otherwise `BETWEEN a AND b`
608                // swallows the AND as a boolean operator.
609                let low = self.parse_bin(3)?;
610                self.expect_kw("AND")?;
611                let high = self.parse_bin(3)?;
612                let ge = Expr::Binary {
613                    op: ">=".into(),
614                    left: Box::new(e.clone()),
615                    right: Box::new(low),
616                };
617                let le = Expr::Binary {
618                    op: "<=".into(),
619                    left: Box::new(e),
620                    right: Box::new(high),
621                };
622                let both = Expr::Binary {
623                    op: "AND".into(),
624                    left: Box::new(ge),
625                    right: Box::new(le),
626                };
627                e = if negated_between {
628                    Expr::Unary { op: "NOT".into(), expr: Box::new(both) }
629                } else {
630                    both
631                };
632                continue;
633            };
634
635            self.expect_punct('(')?;
636            let mut list = vec![];
637            if !self.eat_punct(')') {
638                loop {
639                    list.push(self.parse_expr()?);
640                    if self.eat_punct(',') {
641                        continue;
642                    }
643                    self.expect_punct(')')?;
644                    break;
645                }
646            }
647            e = Expr::InList { expr: Box::new(e), list, negated: negated_in };
648        }
649        Ok(e)
650    }
651
652    fn parse_unary(&mut self) -> Result<Expr> {
653        if self.peek().is_kw("NOT") {
654            self.pos += 1;
655            // NOT binds looser than comparison, so its operand is parsed at
656            // the comparison level: `NOT a = b` is `NOT (a = b)`.
657            let e = self.parse_bin(3)?;
658            return Ok(Expr::Unary { op: "NOT".into(), expr: Box::new(e) });
659        }
660        if self.eat_op("-") {
661            let e = self.parse_unary()?;
662            return Ok(Expr::Unary { op: "-".into(), expr: Box::new(e) });
663        }
664        if self.eat_op("+") {
665            return self.parse_unary();
666        }
667        let atom = self.parse_atom()?;
668        let cast = self.parse_casts(atom)?;
669        // Postfix forms (`IS NULL`, `IN (...)`, `BETWEEN a AND b`) bind to the
670        // OPERAND, before any binary operator is considered.
671        //
672        // They used to be applied after the binary loop in `parse_bin`, which
673        // meant that once `IN (...)` was consumed the loop had already exited
674        // and the rest of the predicate was left unparsed. psql's `\dt` is
675        // `WHERE c.relkind IN (...) AND n.nspname <> '...' AND ...`, so
676        // everything after the IN list silently became "trailing tokens" — and
677        // a WHERE clause that loses its later conjuncts returns TOO MANY rows,
678        // confidently.
679        self.parse_postfix(cast)
680    }
681
682    /// `expr::type`, possibly repeated and possibly `type[]`, and `COLLATE`.
683    fn parse_casts(&mut self, mut e: Expr) -> Result<Expr> {
684        loop {
685            // `COLLATE "C"` — psql writes it throughout `\d`. NEDB has one
686            // collation, so it cannot change the answer; it is consumed rather
687            // than refused, because refusing a clause that provably has no
688            // effect would reject a query whose result is already correct.
689            if self.peek().is_kw("COLLATE") {
690                self.pos += 1;
691                match self.next() {
692                    Tok::Word { .. } | Tok::Quoted(_) => {}
693                    other => bail!("expected a collation name after COLLATE, got {:?}", other),
694                }
695                // A schema-qualified collation: `pg_catalog."C"`.
696                while self.eat_punct('.') {
697                    match self.next() {
698                        Tok::Word { .. } | Tok::Quoted(_) => {}
699                        other => bail!("expected a name after '.', got {:?}", other),
700                    }
701                }
702                continue;
703            }
704            if !self.eat_op("::") {
705                break;
706            }
707            let mut ty = match self.next() {
708                Tok::Word { raw, .. } => raw,
709                Tok::Quoted(s) => s,
710                other => bail!("expected a type name after ::, got {:?}", other),
711            };
712            // A schema-qualified type: `pg_catalog.int2`.
713            while self.eat_punct('.') {
714                match self.next() {
715                    Tok::Word { raw, .. } => ty = raw,
716                    Tok::Quoted(s) => ty = s,
717                    other => bail!("expected a type name after ., got {:?}", other),
718                }
719            }
720            // An array type: `int2[]`.
721            while self.eat_punct('[') {
722                self.expect_punct(']')?;
723                ty.push_str("[]");
724            }
725            e = Expr::Cast { expr: Box::new(e), ty };
726        }
727        Ok(e)
728    }
729
730
731    fn parse_atom(&mut self) -> Result<Expr> {
732        // ( expr ) — or a SUBQUERY, which is named rather than reported as a
733        // stray parenthesis.
734        //
735        // "expected ')', got SELECT" is a parser internal and tells the reader
736        // nothing about what to change. `\d` and `\dp` both hinge on
737        // subqueries, so this is the message somebody will actually read.
738        if self.eat_punct('(') {
739            if self.peek().is_kw("SELECT") {
740                bail!("a subquery is not supported by this SELECT path");
741            }
742            let e = self.parse_expr()?;
743            self.expect_punct(')')?;
744            return Ok(e);
745        }
746
747        // `ARRAY(SELECT ...)` and `EXISTS (SELECT ...)` — both appear in
748        // psql's \dp, and both are subqueries wearing a function's clothes.
749        if self.peek().is_kw("ARRAY") {
750            bail!("the ARRAY(...) constructor is not supported by this SELECT path");
751        }
752        if self.peek().is_kw("EXISTS") {
753            bail!("EXISTS (...) is not supported by this SELECT path");
754        }
755
756        // CASE
757        if self.peek().is_kw("CASE") {
758            return self.parse_case();
759        }
760
761        match self.next() {
762            Tok::Num(n) => Ok(Expr::Literal(from_f64(n))),
763            Tok::Str(s) => Ok(Expr::Literal(Value::String(s))),
764            Tok::Op(o) if o == "*" => Ok(Expr::Star),
765            Tok::Quoted(name) => self.parse_name_tail(None, name),
766            Tok::Word { upper, raw } => match upper.as_str() {
767                "NULL" => Ok(Expr::Literal(Value::Null)),
768                "TRUE" => Ok(Expr::Literal(Value::Bool(true))),
769                "FALSE" => Ok(Expr::Literal(Value::Bool(false))),
770                // `CURRENT_SCHEMA` and friends are functions spelled without
771                // parentheses. Treated as zero-argument calls so one evaluator
772                // handles both spellings.
773                "CURRENT_SCHEMA" | "CURRENT_DATABASE" | "CURRENT_USER" | "SESSION_USER"
774                | "CURRENT_CATALOG" | "USER" | "VERSION"
775                    if !matches!(self.peek(), Tok::Punct('(')) =>
776                {
777                    Ok(Expr::Func { name: upper.to_lowercase(), args: vec![] })
778                }
779                _ => self.parse_name_tail(None, raw),
780            },
781            other => bail!("unexpected {:?} in an expression", other),
782        }
783    }
784
785    /// After an identifier: `.more`, `(args)`, or nothing.
786    ///
787    /// This is where `pg_catalog.pg_get_userbyid(x)` and `n.nspname` and a
788    /// bare `relname` all get told apart, and the rule is positional: the LAST
789    /// dotted part before a `(` is the function name; before anything else it
790    /// is the column, and the part before it is the qualifier.
791    fn parse_name_tail(&mut self, _schema: Option<String>, first: String) -> Result<Expr> {
792        let mut parts = vec![first];
793        while self.eat_punct('.') {
794            // `c.*`
795            if self.eat_op("*") {
796                return Ok(Expr::QualifiedStar(parts.pop().unwrap_or_default()));
797            }
798            match self.next() {
799                Tok::Word { raw, .. } => parts.push(raw),
800                Tok::Quoted(s) => parts.push(s),
801                other => bail!("expected a name after '.', got {:?}", other),
802            }
803        }
804
805        // A call: the last part is the function, any earlier parts are its
806        // schema and are dropped — `pg_catalog.pg_get_userbyid` is the same
807        // function as `pg_get_userbyid`.
808        if matches!(self.peek(), Tok::Punct('(')) {
809            self.pos += 1;
810            let name = parts.pop().unwrap_or_default().to_lowercase();
811            let mut args = vec![];
812            if !self.eat_punct(')') {
813                loop {
814                    // `count(*)`
815                    if self.eat_op("*") {
816                        args.push(Expr::Star);
817                    } else {
818                        args.push(self.parse_expr()?);
819                    }
820                    if self.eat_punct(',') {
821                        continue;
822                    }
823                    self.expect_punct(')')?;
824                    break;
825                }
826            }
827            return Ok(Expr::Func { name, args });
828        }
829
830        let name = parts.pop().unwrap_or_default();
831        // Only the IMMEDIATE qualifier matters: in `public.orders.id` the
832        // binding is `orders`, and the schema is not part of how a column is
833        // addressed.
834        let qual = parts.pop();
835        Ok(Expr::Column { qual, name })
836    }
837
838    fn parse_case(&mut self) -> Result<Expr> {
839        self.expect_kw("CASE")?;
840        // A simple CASE has an operand; a searched CASE goes straight to WHEN.
841        let operand = if self.peek().is_kw("WHEN") {
842            None
843        } else {
844            Some(Box::new(self.parse_expr()?))
845        };
846        let mut whens = vec![];
847        while self.eat_kw("WHEN") {
848            let cond = self.parse_expr()?;
849            self.expect_kw("THEN")?;
850            let then = self.parse_expr()?;
851            whens.push((cond, then));
852        }
853        if whens.is_empty() {
854            bail!("CASE needs at least one WHEN branch");
855        }
856        let else_ = if self.eat_kw("ELSE") {
857            Some(Box::new(self.parse_expr()?))
858        } else {
859            None
860        };
861        self.expect_kw("END")?;
862        Ok(Expr::Case { operand, whens, else_ })
863    }
864
865    // ── the statement ───────────────────────────────────────────────────────
866
867    fn parse_table_ref(&mut self) -> Result<TableRef> {
868        let mut parts = vec![match self.next() {
869            Tok::Word { raw, .. } => raw,
870            Tok::Quoted(s) => s,
871            other => bail!("expected a table name, got {:?}", other),
872        }];
873        while self.eat_punct('.') {
874            match self.next() {
875                Tok::Word { raw, .. } => parts.push(raw),
876                Tok::Quoted(s) => parts.push(s),
877                other => bail!("expected a name after '.', got {:?}", other),
878            }
879        }
880        let name = parts.join(".");
881
882        // `AS alias`, or a bare alias. A bare alias must not swallow a
883        // keyword that starts the next clause, or `FROM t WHERE x` reads `t`
884        // aliased as `WHERE`.
885        let alias = if self.eat_kw("AS") {
886            match self.next() {
887                Tok::Word { raw, .. } => Some(raw),
888                Tok::Quoted(s) => Some(s),
889                other => bail!("expected an alias after AS, got {:?}", other),
890            }
891        } else {
892            match self.peek().clone() {
893                Tok::Word { upper, raw } if !is_clause_keyword(&upper) => {
894                    self.pos += 1;
895                    Some(raw)
896                }
897                Tok::Quoted(s) => {
898                    self.pos += 1;
899                    Some(s)
900                }
901                _ => None,
902            }
903        };
904        Ok(TableRef { name, alias })
905    }
906
907    fn parse_select(&mut self) -> Result<Select> {
908        self.expect_kw("SELECT")?;
909        let distinct = self.eat_kw("DISTINCT");
910        if distinct && self.peek().is_kw("ON") {
911            bail!("DISTINCT ON is not supported");
912        }
913        let _ = self.eat_kw("ALL");
914
915        let mut items = vec![];
916        loop {
917            let expr = self.parse_expr()?;
918            // `AS "Name"`, or a bare alias that is not a clause keyword.
919            let alias = if self.eat_kw("AS") {
920                match self.next() {
921                    Tok::Word { raw, .. } => Some(raw),
922                    Tok::Quoted(s) => Some(s),
923                    other => bail!("expected an alias after AS, got {:?}", other),
924                }
925            } else {
926                match self.peek().clone() {
927                    Tok::Word { upper, raw } if !is_clause_keyword(&upper) => {
928                        self.pos += 1;
929                        Some(raw)
930                    }
931                    Tok::Quoted(s) => {
932                        self.pos += 1;
933                        Some(s)
934                    }
935                    _ => None,
936                }
937            };
938            items.push(SelectItem { expr, alias });
939            if self.eat_punct(',') {
940                continue;
941            }
942            break;
943        }
944
945        let mut from = None;
946        let mut joins = vec![];
947        if self.eat_kw("FROM") {
948            from = Some(self.parse_table_ref()?);
949            // A comma-separated FROM list is an implicit CROSS JOIN.
950            while self.eat_punct(',') {
951                let table = self.parse_table_ref()?;
952                joins.push(Join { kind: JoinKind::Cross, table, on: None });
953            }
954            loop {
955                let kind = if self.peek().is_kw("JOIN") {
956                    self.pos += 1;
957                    JoinKind::Inner
958                } else if self.peek().is_kw("INNER") && self.peek_at(1).is_kw("JOIN") {
959                    self.pos += 2;
960                    JoinKind::Inner
961                } else if self.peek().is_kw("CROSS") && self.peek_at(1).is_kw("JOIN") {
962                    self.pos += 2;
963                    JoinKind::Cross
964                } else if self.peek().is_kw("LEFT") {
965                    self.pos += 1;
966                    let _ = self.eat_kw("OUTER");
967                    self.expect_kw("JOIN")?;
968                    JoinKind::Left
969                } else if self.peek().is_kw("RIGHT") {
970                    self.pos += 1;
971                    let _ = self.eat_kw("OUTER");
972                    self.expect_kw("JOIN")?;
973                    JoinKind::Right
974                } else if self.peek().is_kw("FULL") {
975                    self.pos += 1;
976                    let _ = self.eat_kw("OUTER");
977                    self.expect_kw("JOIN")?;
978                    JoinKind::Full
979                } else {
980                    break;
981                };
982                let table = self.parse_table_ref()?;
983                let on = if self.eat_kw("ON") {
984                    Some(self.parse_expr()?)
985                } else if self.peek().is_kw("USING") {
986                    bail!("JOIN ... USING is not supported — write ON a.col = b.col");
987                } else {
988                    None
989                };
990                if on.is_none() && !matches!(kind, JoinKind::Cross) {
991                    bail!("a {:?} JOIN needs an ON clause", kind);
992                }
993                joins.push(Join { kind, table, on });
994            }
995        }
996
997        let where_ = if self.eat_kw("WHERE") {
998            Some(self.parse_expr()?)
999        } else {
1000            None
1001        };
1002
1003        if self.peek().is_kw("GROUP") {
1004            bail!("GROUP BY is not supported by this SELECT path");
1005        }
1006        if self.peek().is_kw("HAVING") {
1007            bail!("HAVING is not supported by this SELECT path");
1008        }
1009
1010        let mut order_by = vec![];
1011        if self.eat_kw("ORDER") {
1012            self.expect_kw("BY")?;
1013            loop {
1014                // `ORDER BY 1` is an ORDINAL into the select list, not the
1015                // literal 1. Reading it as a constant sorts every row equally
1016                // and silently yields an unordered result.
1017                let (ordinal, expr) = match self.peek().clone() {
1018                    Tok::Num(n)
1019                        if n.fract() == 0.0
1020                            && n >= 1.0
1021                            && !matches!(self.peek_at(1), Tok::Op(_)) =>
1022                    {
1023                        self.pos += 1;
1024                        (Some(n as usize), None)
1025                    }
1026                    _ => (None, Some(self.parse_expr()?)),
1027                };
1028                let dir = if self.eat_kw("DESC") {
1029                    Dir::Desc
1030                } else {
1031                    let _ = self.eat_kw("ASC");
1032                    Dir::Asc
1033                };
1034                // Postgres defaults NULLS LAST for ASC, NULLS FIRST for DESC.
1035                let mut nulls_first = matches!(dir, Dir::Desc);
1036                if self.eat_kw("NULLS") {
1037                    if self.eat_kw("FIRST") {
1038                        nulls_first = true;
1039                    } else if self.eat_kw("LAST") {
1040                        nulls_first = false;
1041                    } else {
1042                        bail!("expected FIRST or LAST after NULLS, got {:?}", self.peek());
1043                    }
1044                }
1045                order_by.push(OrderBy { ordinal, expr, dir, nulls_first });
1046                if self.eat_punct(',') {
1047                    continue;
1048                }
1049                break;
1050            }
1051        }
1052
1053        let mut limit = None;
1054        let mut offset = None;
1055        // Either order, and either may appear alone.
1056        loop {
1057            if self.eat_kw("LIMIT") {
1058                if self.eat_kw("ALL") {
1059                    limit = None;
1060                } else {
1061                    limit = Some(self.parse_count("LIMIT")?);
1062                }
1063                continue;
1064            }
1065            if self.eat_kw("OFFSET") {
1066                offset = Some(self.parse_count("OFFSET")?);
1067                let _ = self.eat_kw("ROW") || self.eat_kw("ROWS");
1068                continue;
1069            }
1070            break;
1071        }
1072
1073        let _ = self.eat_punct(';');
1074        if !matches!(self.peek(), Tok::Eof) {
1075            bail!("unexpected trailing tokens: {:?}", self.peek());
1076        }
1077
1078        Ok(Select { distinct, items, from, joins, where_, order_by, limit, offset })
1079    }
1080
1081    fn parse_count(&mut self, what: &str) -> Result<usize> {
1082        match self.next() {
1083            Tok::Num(n) if n >= 0.0 && n.fract() == 0.0 => Ok(n as usize),
1084            other => bail!("{} expects a non-negative integer, got {:?}", what, other),
1085        }
1086    }
1087}
1088
1089/// Keywords that begin a clause, and so can never be a bare alias.
1090///
1091/// Without this, `FROM pg_class WHERE x` parses `pg_class` aliased as
1092/// `WHERE` — and then the predicate vanishes and every row comes back.
1093fn is_clause_keyword(upper: &str) -> bool {
1094    matches!(
1095        upper,
1096        "FROM" | "WHERE" | "GROUP" | "HAVING" | "ORDER" | "LIMIT" | "OFFSET"
1097            | "JOIN" | "LEFT" | "RIGHT" | "FULL" | "INNER" | "CROSS" | "OUTER"
1098            | "ON" | "USING" | "AND" | "OR" | "AS" | "UNION" | "INTERSECT"
1099            | "EXCEPT" | "FETCH" | "FOR" | "WINDOW" | "RETURNING" | "INTO"
1100            | "ASC" | "DESC" | "NULLS" | "IS" | "IN" | "NOT" | "LIKE" | "ILIKE"
1101            | "BETWEEN" | "THEN" | "WHEN" | "ELSE" | "END" | "CASE" | "DISTINCT"
1102            | "SELECT" | "WITH" | "ALL"
1103    )
1104}
1105
1106/// Parse one `SELECT` statement.
1107pub fn parse(sql: &str) -> Result<Select> {
1108    let toks = lex(sql)?;
1109    let mut p = Parser { toks, pos: 0 };
1110    p.parse_select()
1111}
1112
1113// ─────────────────────────────────────────────────────────────────────────────
1114// Phase 3 — the evaluator
1115// ─────────────────────────────────────────────────────────────────────────────
1116
1117/// One row of a (possibly joined) result: an ordered list of
1118/// `(binding, row-or-NULL)`.
1119///
1120/// `None` is a LEFT JOIN's unmatched side. Keeping it as `None` rather than an
1121/// empty map is what makes `n.nspname IS NULL` answer correctly for a row
1122/// that had no match — an empty map would report the column as absent, which
1123/// looks identical but loses the distinction between "no such column" and "no
1124/// matching row".
1125pub struct Bound<'a> {
1126    pub parts: Vec<(String, Option<&'a Value>)>,
1127}
1128
1129impl<'a> Bound<'a> {
1130    /// Resolve a column reference.
1131    ///
1132    /// A qualified name looks only at its own binding. A bare name scans the
1133    /// bindings in order and takes the first that actually HAS the key —
1134    /// which is how SQL resolves an unambiguous bare column across a join.
1135    fn column(&self, qual: Option<&str>, name: &str) -> Value {
1136        match qual {
1137            Some(q) => {
1138                for (binding, row) in &self.parts {
1139                    if binding.eq_ignore_ascii_case(q) {
1140                        return row
1141                            .and_then(|r| r.get(name))
1142                            .cloned()
1143                            .unwrap_or(Value::Null);
1144                    }
1145                }
1146                Value::Null
1147            }
1148            None => {
1149                for (_, row) in &self.parts {
1150                    if let Some(v) = row.and_then(|r| r.get(name)) {
1151                        return v.clone();
1152                    }
1153                }
1154                Value::Null
1155            }
1156        }
1157    }
1158
1159    /// Is `qual` a binding in this row at all? Used to tell "unknown table
1160    /// alias" (a query bug, worth an error) from "column absent in this row"
1161    /// (ordinary schemaless behaviour, worth a NULL).
1162    fn has_binding(&self, qual: &str) -> bool {
1163        self.parts.iter().any(|(b, _)| b.eq_ignore_ascii_case(qual))
1164    }
1165
1166    /// Every column of every bound row, for `SELECT *`.
1167    fn flatten(&self) -> Vec<(String, Value)> {
1168        let mut out = vec![];
1169        for (_, row) in &self.parts {
1170            if let Some(Value::Object(m)) = row {
1171                for (k, v) in m {
1172                    out.push((k.clone(), v.clone()));
1173                }
1174            }
1175        }
1176        out
1177    }
1178
1179    fn flatten_binding(&self, qual: &str) -> Vec<(String, Value)> {
1180        let mut out = vec![];
1181        for (binding, row) in &self.parts {
1182            if binding.eq_ignore_ascii_case(qual) {
1183                if let Some(Value::Object(m)) = row {
1184                    for (k, v) in m {
1185                        out.push((k.clone(), v.clone()));
1186                    }
1187                }
1188            }
1189        }
1190        out
1191    }
1192}
1193
1194/// SQL truth: three-valued. `None` is UNKNOWN.
1195///
1196/// This is not pedantry. A LEFT JOIN produces NULL columns, and a predicate
1197/// over NULL must be UNKNOWN rather than false — because `NOT UNKNOWN` is
1198/// UNKNOWN, not true. Collapsing UNKNOWN to false would make
1199/// `WHERE NOT (n.nspname = 'x')` include unmatched rows that Postgres
1200/// excludes, and the row counts would silently disagree.
1201type Truth = Option<bool>;
1202
1203fn truthy(v: &Value) -> Truth {
1204    match v {
1205        Value::Null => None,
1206        Value::Bool(b) => Some(*b),
1207        // A predicate position holding a non-boolean is a query error in
1208        // Postgres. Being lenient here would let `WHERE 1` mean something
1209        // different than it does there, so it is treated as UNKNOWN.
1210        _ => None,
1211    }
1212}
1213
1214/// Compare two values for ordering and equality.
1215///
1216/// Numbers compare numerically, strings lexicographically, booleans false <
1217/// true. A number and a numeric-looking string compare NUMERICALLY, because
1218/// catalogue rows carry oids as numbers while a client may quote them.
1219fn cmp_values(a: &Value, b: &Value) -> Option<std::cmp::Ordering> {
1220    use std::cmp::Ordering;
1221    match (a, b) {
1222        (Value::Null, _) | (_, Value::Null) => None,
1223        (Value::Number(x), Value::Number(y)) => {
1224            x.as_f64().partial_cmp(&y.as_f64())
1225        }
1226        (Value::String(x), Value::String(y)) => Some(x.cmp(y)),
1227        (Value::Bool(x), Value::Bool(y)) => Some(x.cmp(y)),
1228        // Mixed number/string: try numeric first, then fall back to text, so
1229        // `oid = '16384'` behaves the way a Postgres client expects.
1230        (Value::Number(x), Value::String(y)) => match y.parse::<f64>() {
1231            Ok(n) => x.as_f64().partial_cmp(&Some(n)),
1232            Err(_) => Some(as_text(a).cmp(&as_text(b))),
1233        },
1234        (Value::String(x), Value::Number(y)) => match x.parse::<f64>() {
1235            Ok(n) => Some(n).partial_cmp(&y.as_f64()),
1236            Err(_) => Some(as_text(a).cmp(&as_text(b))),
1237        },
1238        _ => {
1239            let (x, y) = (as_text(a), as_text(b));
1240            if x == y { Some(Ordering::Equal) } else { Some(x.cmp(&y)) }
1241        }
1242    }
1243}
1244
1245/// The text a user sees for a value — not its JSON encoding.
1246fn as_text(v: &Value) -> String {
1247    match v {
1248        Value::String(s) => s.clone(),
1249        Value::Null => String::new(),
1250        Value::Bool(b) => (if *b { "t" } else { "f" }).to_string(),
1251        other => other.to_string(),
1252    }
1253}
1254
1255fn num(v: &Value) -> Option<f64> {
1256    match v {
1257        Value::Number(n) => n.as_f64(),
1258        Value::String(s) => s.parse().ok(),
1259        Value::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
1260        _ => None,
1261    }
1262}
1263
1264/// Every number this engine PRODUCES goes through here, so that one rule
1265/// decides how numbers render.
1266///
1267/// An integral value becomes a JSON integer. Without this, the lexer's `f64`
1268/// leaked into the output and `SELECT 1` answered `1.0` — which a client reads
1269/// as the TEXT "1.0", where PostgreSQL says "1". The liveness probe every
1270/// driver opens with was the most visible casualty.
1271///
1272/// Note what this rule cannot do: PostgreSQL distinguishes `1` (integer) from
1273/// `1.0` (numeric with scale 1), and JSON has no numeric-with-scale type at
1274/// all, so that distinction is unrepresentable here whatever we choose.
1275/// Rendering integral values as integers is the only self-consistent option
1276/// available, and it is the one that matches the common case.
1277fn from_f64(f: f64) -> Value {
1278    if f.is_finite() && f.fract() == 0.0 && f >= i64::MIN as f64 && f <= i64::MAX as f64 {
1279        return Value::Number((f as i64).into());
1280    }
1281    serde_json::Number::from_f64(f).map(Value::Number).unwrap_or(Value::Null)
1282}
1283
1284/// Evaluate an expression against one bound row.
1285pub fn eval(e: &Expr, row: &Bound) -> Result<Value> {
1286    Ok(match e {
1287        Expr::Literal(v) => v.clone(),
1288
1289        Expr::Column { qual, name } => {
1290            // An unknown ALIAS is a query bug and is reported. An unknown
1291            // COLUMN in a known binding is NULL, because a schemaless
1292            // document may legitimately omit any field.
1293            if let Some(q) = qual {
1294                if !row.has_binding(q) {
1295                    bail!("no table or alias named {:?} in this query", q);
1296                }
1297            }
1298            row.column(qual.as_deref(), name)
1299        }
1300
1301        Expr::Cast { expr, .. } => eval(expr, row)?,
1302
1303        Expr::Star | Expr::QualifiedStar(_) => {
1304            bail!("`*` is only valid in a select list or as count(*)")
1305        }
1306
1307        Expr::Unary { op, expr } => {
1308            let v = eval(expr, row)?;
1309            match op.as_str() {
1310                "NOT" => match truthy(&v) {
1311                    // NOT UNKNOWN is UNKNOWN, not true.
1312                    None => Value::Null,
1313                    Some(b) => Value::Bool(!b),
1314                },
1315                "-" => match num(&v) {
1316                    Some(n) => from_f64(-n),
1317                    None => Value::Null,
1318                },
1319                other => bail!("unsupported unary operator {:?}", other),
1320            }
1321        }
1322
1323        Expr::Binary { op, left, right } => {
1324            // AND / OR short-circuit on the value that decides the result, and
1325            // follow SQL's three-valued truth tables:
1326            //   false AND unknown = false      true  OR unknown = true
1327            //   true  AND unknown = unknown    false OR unknown = unknown
1328            if op == "AND" {
1329                let l = truthy(&eval(left, row)?);
1330                if l == Some(false) {
1331                    return Ok(Value::Bool(false));
1332                }
1333                let r = truthy(&eval(right, row)?);
1334                return Ok(match (l, r) {
1335                    (_, Some(false)) => Value::Bool(false),
1336                    (Some(true), Some(true)) => Value::Bool(true),
1337                    _ => Value::Null,
1338                });
1339            }
1340            if op == "OR" {
1341                let l = truthy(&eval(left, row)?);
1342                if l == Some(true) {
1343                    return Ok(Value::Bool(true));
1344                }
1345                let r = truthy(&eval(right, row)?);
1346                return Ok(match (l, r) {
1347                    (_, Some(true)) => Value::Bool(true),
1348                    (Some(false), Some(false)) => Value::Bool(false),
1349                    _ => Value::Null,
1350                });
1351            }
1352
1353            let l = eval(left, row)?;
1354            let r = eval(right, row)?;
1355
1356            // Every comparison over NULL is UNKNOWN — including `NULL = NULL`.
1357            let compare = |ord: fn(std::cmp::Ordering) -> bool| -> Value {
1358                match cmp_values(&l, &r) {
1359                    None => Value::Null,
1360                    Some(o) => Value::Bool(ord(o)),
1361                }
1362            };
1363
1364            match op.as_str() {
1365                "=" => compare(|o| o.is_eq()),
1366                "!=" | "<>" => compare(|o| o.is_ne()),
1367                "<" => compare(|o| o.is_lt()),
1368                "<=" => compare(|o| o.is_le()),
1369                ">" => compare(|o| o.is_gt()),
1370                ">=" => compare(|o| o.is_ge()),
1371
1372                "~" | "~*" | "!~" | "!~*" => {
1373                    if l.is_null() || r.is_null() {
1374                        Value::Null
1375                    } else {
1376                        let pat = as_text(&r);
1377                        if let Some(bad) = crate::nql::unsupported_regex_char_pub(&pat) {
1378                            bail!(
1379                                "regex {:?} uses {:?}, which this engine does not \
1380                                 implement. The supported subset is ^ $ . and \
1381                                 literal text",
1382                                pat, bad
1383                            );
1384                        }
1385                        let hit = crate::nql::regex_match_pub(
1386                            &as_text(&l), &pat, op.ends_with('*'));
1387                        Value::Bool(hit != op.starts_with('!'))
1388                    }
1389                }
1390
1391                "LIKE" | "ILIKE" | "NOT LIKE" | "NOT ILIKE" => {
1392                    if l.is_null() || r.is_null() {
1393                        Value::Null
1394                    } else {
1395                        let hit = crate::nql::like_match_pub(
1396                            &as_text(&l), &as_text(&r), op.ends_with("ILIKE"));
1397                        Value::Bool(hit != op.starts_with("NOT"))
1398                    }
1399                }
1400
1401                // String concatenation. NULL propagates, as in Postgres.
1402                "||" => {
1403                    if l.is_null() || r.is_null() {
1404                        Value::Null
1405                    } else {
1406                        Value::String(format!("{}{}", as_text(&l), as_text(&r)))
1407                    }
1408                }
1409
1410                "+" | "-" | "*" | "/" | "%" => match (num(&l), num(&r)) {
1411                    (Some(a), Some(b)) => match op.as_str() {
1412                        "+" => from_f64(a + b),
1413                        "-" => from_f64(a - b),
1414                        "*" => from_f64(a * b),
1415                        // Division by zero is an ERROR in Postgres, not
1416                        // infinity. Returning inf would be a wrong number.
1417                        "/" if b == 0.0 => bail!("division by zero"),
1418                        "/" => from_f64(a / b),
1419                        "%" if b == 0.0 => bail!("division by zero"),
1420                        "%" => from_f64(a % b),
1421                        _ => unreachable!(),
1422                    },
1423                    _ => Value::Null,
1424                },
1425
1426                other => bail!("unsupported operator {:?}", other),
1427            }
1428        }
1429
1430        Expr::IsNull { expr, negated } => {
1431            let v = eval(expr, row)?;
1432            // `IS NULL` is the one predicate that is never UNKNOWN — it always
1433            // answers true or false, which is exactly why it exists.
1434            Value::Bool(v.is_null() != *negated)
1435        }
1436
1437        Expr::InList { expr, list, negated } => {
1438            let v = eval(expr, row)?;
1439            if v.is_null() {
1440                return Ok(Value::Null);
1441            }
1442            let mut any_null = false;
1443            let mut found = false;
1444            for item in list {
1445                let iv = eval(item, row)?;
1446                if iv.is_null() {
1447                    any_null = true;
1448                    continue;
1449                }
1450                if matches!(cmp_values(&v, &iv), Some(std::cmp::Ordering::Equal)) {
1451                    found = true;
1452                    break;
1453                }
1454            }
1455            // `x NOT IN (1, NULL)` is UNKNOWN rather than true when x is not
1456            // 1 — because x might equal the NULL. Postgres agrees, and this
1457            // is the classic NOT IN trap.
1458            if found {
1459                Value::Bool(!*negated)
1460            } else if any_null {
1461                Value::Null
1462            } else {
1463                Value::Bool(*negated)
1464            }
1465        }
1466
1467        Expr::Case { operand, whens, else_ } => {
1468            let subject = match operand {
1469                Some(o) => Some(eval(o, row)?),
1470                None => None,
1471            };
1472            for (cond, then) in whens {
1473                let hit = match &subject {
1474                    // simple CASE: compare the operand to each WHEN value.
1475                    Some(sv) => {
1476                        let cv = eval(cond, row)?;
1477                        matches!(cmp_values(sv, &cv), Some(std::cmp::Ordering::Equal))
1478                    }
1479                    // searched CASE: each WHEN is a predicate, and UNKNOWN
1480                    // does not match.
1481                    None => truthy(&eval(cond, row)?) == Some(true),
1482                };
1483                if hit {
1484                    return eval(then, row);
1485                }
1486            }
1487            match else_ {
1488                Some(e) => eval(e, row)?,
1489                // A CASE with no matching branch and no ELSE is NULL, which is
1490                // exactly what psql's \dt relies on for an unknown relkind.
1491                None => Value::Null,
1492            }
1493        }
1494
1495        Expr::Func { name, args } => eval_func(name, args, row)?,
1496    })
1497}
1498
1499/// Scalar functions.
1500///
1501/// Only what real clients actually call. An unknown function is REFUSED by
1502/// name rather than returning NULL — a NULL would flow into a result set as a
1503/// blank column and look like missing data rather than a missing feature.
1504fn eval_func(name: &str, args: &[Expr], row: &Bound) -> Result<Value> {
1505    // Evaluated lazily per arm, because `coalesce` must not error on a later
1506    // argument once an earlier one is non-null.
1507    let arg = |i: usize| -> Result<Value> {
1508        match args.get(i) {
1509            Some(e) => eval(e, row),
1510            None => Ok(Value::Null),
1511        }
1512    };
1513
1514    Ok(match name {
1515        // ── identity / session ──────────────────────────────────────────────
1516        // NEDB presents a single role and a single schema; reporting them
1517        // consistently is what lets a client's "who am I" probe succeed.
1518        "pg_get_userbyid" | "current_user" | "session_user" | "user" => {
1519            Value::String("nedb".into())
1520        }
1521        "current_schema" => Value::String("public".into()),
1522        "current_database" | "current_catalog" => Value::String("nedb".into()),
1523        "version" => Value::String(crate::pgwire::version_string()),
1524
1525        // ── visibility ──────────────────────────────────────────────────────
1526        // Every relation NEDB reports is in `public` and reachable on the
1527        // search path, so visibility is unconditionally true. Returning false
1528        // would hide every table from `\dt`.
1529        "pg_table_is_visible" | "pg_type_is_visible" | "pg_function_is_visible"
1530        | "pg_opclass_is_visible" | "pg_conversion_is_visible" => Value::Bool(true),
1531
1532        // ── encoding ────────────────────────────────────────────────────────
1533        "pg_encoding_to_char" => Value::String("UTF8".into()),
1534        "pg_get_expr" | "pg_get_indexdef" | "pg_get_constraintdef"
1535        | "pg_get_viewdef" | "pg_get_partkeydef" | "obj_description"
1536        | "col_description" | "shobj_description" => Value::Null,
1537
1538        // ── text ────────────────────────────────────────────────────────────
1539        "lower" => match arg(0)? {
1540            Value::Null => Value::Null,
1541            v => Value::String(as_text(&v).to_lowercase()),
1542        },
1543        "upper" => match arg(0)? {
1544            Value::Null => Value::Null,
1545            v => Value::String(as_text(&v).to_uppercase()),
1546        },
1547        "length" | "char_length" | "character_length" => match arg(0)? {
1548            Value::Null => Value::Null,
1549            v => from_f64(as_text(&v).chars().count() as f64),
1550        },
1551        "format_type" => match arg(0)? {
1552            Value::Null => Value::Null,
1553            v => Value::String(crate::pgcatalog::type_name_pub(
1554                num(&v).unwrap_or(25.0) as i32).to_string()),
1555        },
1556        "array_to_string" | "pg_catalog.array_to_string" => {
1557            // NEDB stores no arrays in the catalogue, so an ACL column is
1558            // NULL and joining it yields NULL — the same as Postgres for a
1559            // relation with default privileges.
1560            match arg(0)? {
1561                Value::Array(items) => {
1562                    let sep = as_text(&arg(1)?);
1563                    Value::String(
1564                        items.iter().map(as_text).collect::<Vec<_>>().join(&sep),
1565                    )
1566                }
1567                _ => Value::Null,
1568            }
1569        }
1570        "quote_ident" => Value::String(as_text(&arg(0)?)),
1571
1572        // ── null handling ───────────────────────────────────────────────────
1573        "coalesce" => {
1574            let mut out = Value::Null;
1575            for a in args {
1576                let v = eval(a, row)?;
1577                if !v.is_null() {
1578                    out = v;
1579                    break;
1580                }
1581            }
1582            out
1583        }
1584        "nullif" => {
1585            let a = arg(0)?;
1586            let b = arg(1)?;
1587            if matches!(cmp_values(&a, &b), Some(std::cmp::Ordering::Equal)) {
1588                Value::Null
1589            } else {
1590                a
1591            }
1592        }
1593
1594        // ── casts spelled as functions ──────────────────────────────────────
1595        "int4" | "int8" | "int2" => match num(&arg(0)?) {
1596            Some(n) => from_f64(n.trunc()),
1597            None => Value::Null,
1598        },
1599        "text" => match arg(0)? {
1600            Value::Null => Value::Null,
1601            v => Value::String(as_text(&v)),
1602        },
1603
1604        other => bail!(
1605            "the function {}() is not implemented. It is refused rather than \
1606             answered with NULL, because a NULL column reads as missing DATA \
1607             rather than a missing feature",
1608            other
1609        ),
1610    })
1611}
1612
1613// ─────────────────────────────────────────────────────────────────────────────
1614// Phase 4 — execution
1615// ─────────────────────────────────────────────────────────────────────────────
1616
1617/// Every relation in a query must be addressable by a DISTINCT name.
1618///
1619/// PostgreSQL rejects `FROM a JOIN a` with "table name a specified more than
1620/// once". This engine used to accept it and answer WRONGLY: a qualified
1621/// reference scans the bindings in order and takes the first match, so both
1622/// `a.x` and `a.y` read the same row, and `FROM emp JOIN emp ON emp.mgr =
1623/// emp.id` compared every row to ITSELF and returned no rows at all.
1624///
1625/// A silently empty result is the worst possible answer — it is
1626/// indistinguishable from "there is no such data". Refusing is strictly
1627/// better, and the supported spelling is one alias per relation.
1628fn validate_bindings(sel: &Select) -> Result<()> {
1629    let mut seen: Vec<String> = vec![];
1630    if let Some(f) = &sel.from {
1631        seen.push(f.binding());
1632    }
1633    for j in &sel.joins {
1634        seen.push(j.table.binding());
1635    }
1636    for (i, b) in seen.iter().enumerate() {
1637        if let Some(prev) = seen[..i].iter().find(|p| p.eq_ignore_ascii_case(b)) {
1638            bail!(
1639                "ambiguous relation binding: {:?} appears more than once; use \
1640                 aliases (for example `FROM {} JOIN {} AS {}2 ...`)",
1641                prev, prev, prev, prev
1642            );
1643        }
1644    }
1645    Ok(())
1646}
1647
1648/// One output column: the key it is stored under, and the name the client sees.
1649///
1650/// These are NOT always the same, and that is the whole point. PostgreSQL
1651/// permits duplicate output names — `SELECT e.name, e2.name` legitimately
1652/// returns two columns both called `name`, and generated SQL relies on it.
1653/// Rows here are JSON objects, so two columns sharing a key would share a
1654/// VALUE: the second write silently overwrote the first, and the query above
1655/// returned the same value twice while reporting two columns.
1656///
1657/// So the key is made unique and the display name is left alone. Renaming the
1658/// column instead would be worse — generated SQL asks for the name it wrote.
1659#[derive(Debug, Clone, PartialEq, Eq)]
1660pub struct OutCol {
1661    pub key: String,
1662    pub name: String,
1663}
1664
1665/// A key no user field can collide with, for the second and later columns
1666/// sharing a display name. `\u{1}` is not producible in a JSON field name by
1667/// any sane writer, and the index disambiguates even if one managed it.
1668fn unique_key(taken: &[OutCol], name: &str) -> String {
1669    if !taken.iter().any(|c| c.key == name) {
1670        return name.to_string();
1671    }
1672    format!("{name}\u{1}{}", taken.len())
1673}
1674
1675/// A joined row, owned: `(binding, row-or-NULL)` per source table.
1676type JoinedRow = Vec<(String, Option<Value>)>;
1677
1678fn bind<'a>(row: &'a JoinedRow) -> Bound<'a> {
1679    Bound {
1680        parts: row.iter().map(|(b, v)| (b.clone(), v.as_ref())).collect(),
1681    }
1682}
1683
1684/// The name a client sees for a select item, when no `AS` was given.
1685///
1686/// Postgres derives it: a bare column keeps its column name, a function call
1687/// takes the function's name, and anything else becomes `?column?`. Matching
1688/// that matters because clients index result columns BY NAME — psycopg's
1689/// `RealDictCursor` and every ORM do — so inventing a different name breaks
1690/// code that would work against Postgres.
1691fn derived_name(e: &Expr) -> String {
1692    match e {
1693        Expr::Column { name, .. } => name.clone(),
1694        Expr::Func { name, .. } => name.clone(),
1695        Expr::Cast { expr, .. } => derived_name(expr),
1696        Expr::Case { .. } => "case".to_string(),
1697        _ => "?column?".to_string(),
1698    }
1699}
1700
1701/// A relation, delivered one row at a time.
1702///
1703/// # The smallest interface that permits early termination
1704///
1705/// The previous contract handed back an owned `Vec<Value>`, which forced the
1706/// whole relation to exist before any work could start. That is fine until
1707/// execution can stop early — and once `LIMIT` can stop a join, a contract
1708/// that insists on materialising 8000 rows to return 20 becomes the
1709/// bottleneck. It was measured as exactly that: after the filter fusion in
1710/// #120, the hash path's remaining time was dominated by cloning relations
1711/// rather than probing them.
1712///
1713/// So this is deliberately two methods, not an async stream and not a
1714/// borrowing iterator with a lifetime parameter threaded through the whole
1715/// evaluator. Pull a row; stop whenever you like by dropping it.
1716///
1717/// [`size_hint`](Relation::size_hint) exists only so the join planner can
1718/// keep choosing a strategy from relation sizes. A source that genuinely does
1719/// not know returns `None`, and the planner then decides from what it does
1720/// know rather than pretending.
1721pub trait Relation {
1722    /// The next row, or `None` when exhausted.
1723    fn next_row(&mut self) -> Result<Option<Value>>;
1724
1725    /// Exact row count when the source knows it, `None` when it does not.
1726    fn size_hint(&self) -> Option<usize> {
1727        None
1728    }
1729}
1730
1731/// A relation backed by an already-materialised `Vec`.
1732///
1733/// Every current caller uses this, so the interface change on its own alters
1734/// no behaviour — it is what lets the executor become demand-driven ahead of
1735/// the storage layer, rather than requiring both to move at once.
1736pub struct VecRelation {
1737    iter: std::vec::IntoIter<Value>,
1738    len: usize,
1739}
1740
1741impl Relation for VecRelation {
1742    fn next_row(&mut self) -> Result<Option<Value>> {
1743        Ok(self.iter.next())
1744    }
1745    fn size_hint(&self) -> Option<usize> {
1746        Some(self.len)
1747    }
1748}
1749
1750/// Wrap a materialised relation.
1751pub fn from_vec(rows: Vec<Value>) -> Box<dyn Relation> {
1752    let len = rows.len();
1753    Box::new(VecRelation { iter: rows.into_iter(), len })
1754}
1755
1756/// Everything one execution needs from the outside world.
1757///
1758/// A callback rather than a concrete store, which is what lets this engine
1759/// serve synthesised catalogue relations today and stored collections later
1760/// without knowing the difference.
1761pub type Resolver<'r> = dyn Fn(&str) -> Result<Option<Box<dyn Relation>>> + 'r;
1762
1763/// Run a parsed `SELECT`, returning `(column names, rows)`.
1764///
1765/// Rows come back as JSON objects keyed by output column name, which is the
1766/// shape the wire encoder already consumes.
1767pub fn execute(sel: &Select, resolve: &Resolver) -> Result<(Vec<OutCol>, Vec<Value>)> {
1768    let (cols, rows, _) = execute_explain(sel, resolve, JoinExec::Auto)?;
1769    Ok((cols, rows))
1770}
1771
1772/// Run a parsed `SELECT`, also reporting how each join was executed.
1773///
1774/// `exec` forces a join strategy, which exists so that differential tests can
1775/// drive the SAME query down BOTH paths — and so a benchmark can prove it
1776/// measured the path it claims to have measured rather than silently timing
1777/// the other one twice.
1778pub fn execute_explain(
1779    sel: &Select,
1780    resolve: &Resolver,
1781    exec: JoinExec,
1782) -> Result<(Vec<OutCol>, Vec<Value>, Plan)> {
1783    execute_with(sel, resolve, exec, true)
1784}
1785
1786/// Execution options. Every switch exists so a differential test can run the
1787/// SAME query with the optimisation on and off and compare — without that, a
1788/// test believing it exercised an optimisation could be measuring the
1789/// unoptimised path, and the equivalence suite would prove nothing.
1790#[derive(Debug, Clone, Copy)]
1791pub struct Opts {
1792    pub exec: JoinExec,
1793    pub pushdown: bool,
1794    /// Evaluate the `WHERE` clause inside the final join rather than as a
1795    /// separate pass. Semantically identical; it is what lets the row budget
1796    /// apply to a filtered join.
1797    pub fuse_filter: bool,
1798}
1799
1800impl Default for Opts {
1801    fn default() -> Self {
1802        Opts { exec: JoinExec::Auto, pushdown: true, fuse_filter: true }
1803    }
1804}
1805
1806impl Opts {
1807    pub fn exec(exec: JoinExec) -> Self {
1808        Opts { exec, ..Default::default() }
1809    }
1810}
1811
1812/// As [`execute_explain`], with predicate pushdown switchable.
1813///
1814/// The switch exists so differential tests can run the SAME query with and
1815/// without the rewrite and compare. Without it, a test believing it exercised
1816/// pushdown could be measuring the unoptimised path, and the equivalence suite
1817/// would prove nothing — the same reason `JoinExec` can force a strategy.
1818pub fn execute_with(
1819    sel: &Select,
1820    resolve: &Resolver,
1821    exec: JoinExec,
1822    pushdown: bool,
1823) -> Result<(Vec<OutCol>, Vec<Value>, Plan)> {
1824    execute_opts(sel, resolve, Opts { exec, pushdown, ..Default::default() })
1825}
1826
1827/// The full form.
1828pub fn execute_opts(
1829    sel: &Select,
1830    resolve: &Resolver,
1831    opts: Opts,
1832) -> Result<(Vec<OutCol>, Vec<Value>, Plan)> {
1833    let exec = opts.exec;
1834    let pushdown = opts.pushdown;
1835    let mut plan = Plan::default();
1836
1837    // ── 0a. semantic validation, before any work ────────────────────────────
1838    validate_bindings(sel)?;
1839
1840    // ── 0. the row budget ───────────────────────────────────────────────────
1841    //
1842    // The only safe rewrite available without a streaming executor: when the
1843    // final answer is a PREFIX of the join's output, the join may stop as soon
1844    // as it has produced enough rows.
1845    //
1846    // Every one of these conditions is load-bearing, and each corresponds to
1847    // an operation that can REDUCE the row count after the join — capping the
1848    // join's output early would then starve it:
1849    //
1850    //   * `ORDER BY` — the prefix depends on the sort, not on emission order.
1851    //   * `DISTINCT` — deduplication can shrink 100 rows to 3.
1852    //   * a `WHERE` clause — filtering happens after the join here.
1853    //   * more than one join — an intermediate cap can starve a later join.
1854    //
1855    // `OFFSET` is added to the budget rather than disqualifying it, because
1856    // the rows skipped still have to be produced.
1857    //
1858    // This is narrow on purpose. `SELECT ... JOIN ... LIMIT n` is the shape an
1859    // interactive client sends constantly, and it was measured taking 32ms to
1860    // return 20 rows out of an 8000-row join. A wider rewrite needs a
1861    // streaming executor, not a cleverer predicate.
1862    // Fusing the `WHERE` into the final join is what makes a filtered query
1863    // eligible: the join's own output is then already filtered, so its length
1864    // is a real count of final rows and stopping early keeps a true prefix.
1865    // Without the fusion a `WHERE` had to disqualify the budget entirely.
1866    let fuse = opts.fuse_filter && sel.where_.is_some() && !sel.joins.is_empty();
1867
1868    let budget: Option<usize> = match sel.limit {
1869        Some(lim)
1870            if sel.order_by.is_empty()
1871                && !sel.distinct
1872                && !sel.joins.is_empty()
1873                && (sel.where_.is_none() || fuse) =>
1874        {
1875            Some(lim.saturating_add(sel.offset.unwrap_or(0)))
1876        }
1877        _ => None,
1878    };
1879    plan.budget = budget;
1880
1881    // ── 0b. predicate pushdown ──────────────────────────────────────────────
1882    // Conjuncts of the WHERE clause that read exactly one relation are COPIED
1883    // to pre-filter that relation before the join. The WHERE clause below is
1884    // untouched and still runs afterwards — a copy, never a move, which is
1885    // what keeps this safe for outer joins. See `sqlpush` for the argument.
1886    let all_bindings: Vec<String> = sel
1887        .from
1888        .iter()
1889        .map(|t| t.binding())
1890        .chain(sel.joins.iter().map(|j| j.table.binding()))
1891        .collect();
1892    let nullable = crate::sqlpush::nullable_bindings(sel);
1893    let push = if pushdown {
1894        crate::sqlpush::plan(sel.where_.as_ref(), &all_bindings, &nullable)
1895    } else {
1896        Pushdown::default()
1897    };
1898    plan.refusals = push.refusals.clone();
1899
1900    let mut base_scan_at: Option<usize> = None;
1901    let mut base_prefilter_at: Option<usize> = None;
1902
1903    // ── 1. source rows, and the join ────────────────────────────────────────
1904    //
1905    // The driving relation is STREAMED when there is a join to feed it into,
1906    // so a query that stops early never asks the source for the rest. The
1907    // inner side of each join is materialised, because it genuinely has to
1908    // be: a hash join builds its table before probing, and a nested loop
1909    // re-scans it per left row.
1910    let mut left_src: Box<dyn LeftSource> = match &sel.from {
1911        None => {
1912            // `SELECT 1` with no FROM is one row with no columns — which is
1913            // how a client's liveness probe is written.
1914            Box::new(VecLeft { rows: vec![vec![]], at: 0 })
1915        }
1916        Some(t) => {
1917            let rel = fetch(&t.name, resolve)?;
1918            let binding = t.binding();
1919            // Placeholder counts, patched once the pull is over. A streamed
1920            // relation cannot report its `actual rows` before it is read, and
1921            // inventing a number would be exactly the kind of plausible
1922            // fiction `EXPLAIN` must never contain.
1923            base_scan_at = Some(plan.stages.len());
1924            plan.push(Stage::Scan {
1925                table: t.name.clone(),
1926                binding: binding.clone(),
1927                rows: 0,
1928            });
1929            let preds = push.for_binding(&binding).cloned().unwrap_or_default();
1930            if !preds.is_empty() {
1931                base_prefilter_at = Some(plan.stages.len());
1932                plan.push(Stage::Prefilter {
1933                    binding: binding.clone(),
1934                    predicates: preds.len(),
1935                    in_rows: 0,
1936                    out_rows: 0,
1937                });
1938            }
1939            Box::new(StreamLeft { rel, binding, preds, pulled: 0, kept: 0 })
1940        }
1941    };
1942
1943    // The bindings accumulated so far, tracked explicitly rather than read off
1944    // the first row. Reading a row cannot describe the shape when there are no
1945    // rows — which is exactly the case a `RIGHT JOIN` onto an EMPTY left
1946    // relation produces, and it made those rows come back missing their left
1947    // bindings entirely instead of carrying them as NULL.
1948    let mut left_bindings: Vec<String> = match &sel.from {
1949        None => vec![],
1950        Some(t) => vec![t.binding()],
1951    };
1952    let last = sel.joins.len().saturating_sub(1);
1953    let mut rows: Vec<JoinedRow> = vec![];
1954    let mut base_pulled: Option<usize> = None;
1955    let mut base_kept: Option<usize> = None;
1956
1957    for (ji, join) in sel.joins.iter().enumerate() {
1958        let right_rel = fetch(&join.table.name, resolve)?;
1959        let rb = join.table.binding();
1960        let right_all = drain(right_rel)?;
1961        plan.push(Stage::Scan {
1962            table: join.table.name.clone(),
1963            binding: rb.clone(),
1964            rows: right_all.len(),
1965        });
1966        let right_rows = prefilter(right_all, &rb, &push, &mut plan)?;
1967
1968        // The filter can only be evaluated once every binding it reads is
1969        // bound, so it fuses into the FINAL join and nowhere earlier. The
1970        // budget likewise applies only there: capping an intermediate join
1971        // can starve a later one of rows it needed.
1972        let is_last = ji == last;
1973        let post = if fuse && is_last { sel.where_.as_ref() } else { None };
1974        let join_budget = if is_last { budget } else { None };
1975
1976        // The planner proposes; sizes decide. A join with no provable equality
1977        // key has nothing to hash on and stays on the reference path.
1978        let keys = sqljoin::hash_keys(join.on.as_ref(), &left_bindings, &rb);
1979        let left_hint = left_src.hint().unwrap_or(usize::MAX);
1980        let strategy = sqljoin::choose(exec, keys.len(), left_hint, right_rows.len());
1981
1982        let (out, removed, consumed) = match strategy {
1983            Strategy::NestedLoop => join_nested_loop(
1984                left_src.as_mut(), &left_bindings, join, &right_rows, &rb,
1985                join_budget, post,
1986            )?,
1987            Strategy::Hash => join_hash(
1988                left_src.as_mut(), &left_bindings, join, &right_rows, &rb, &keys,
1989                join_budget, post,
1990            )?,
1991        };
1992
1993        plan.push(Stage::Join {
1994            kind: join.kind,
1995            table: join.table.name.clone(),
1996            binding: rb.clone(),
1997            strategy,
1998            keys: keys.len(),
1999            left_rows: consumed,
2000            right_rows: right_rows.len(),
2001            out_rows: out.len(),
2002            early_stopped: join_budget.is_some_and(|b| out.len() >= b),
2003            post_filter_removed: post.map(|_| removed),
2004        });
2005        left_bindings.push(rb);
2006        // Read the streamed base's counts BEFORE the source is replaced.
2007        if ji == 0 {
2008            if let Some((pulled, kept)) = left_src.stats() {
2009                base_pulled = Some(pulled);
2010                base_kept = Some(kept);
2011            }
2012        }
2013        rows = out;
2014        // The next join reads this join's output, which is already whole.
2015        left_src = Box::new(VecLeft { rows: std::mem::take(&mut rows), at: 0 });
2016    }
2017
2018    // Recover the rows from the last source, and record what the streamed
2019    // base relation actually delivered.
2020    rows = left_src.take_rows();
2021    if let Some(i) = base_scan_at {
2022        if let (Some(pulled), Some(kept)) = (base_pulled, base_kept) {
2023            if let Some(Stage::Scan { rows: r, .. }) = plan.stages.get_mut(i) {
2024                *r = pulled;
2025            }
2026            if let Some(j) = base_prefilter_at {
2027                if let Some(Stage::Prefilter { in_rows, out_rows, .. }) =
2028                    plan.stages.get_mut(j)
2029                {
2030                    *in_rows = pulled;
2031                    *out_rows = kept;
2032                }
2033            }
2034        }
2035    }
2036
2037    // ── 2. WHERE ────────────────────────────────────────────────────────────
2038    if let Some(pred) = sel.where_.as_ref().filter(|_| !fuse) {
2039        let in_rows = rows.len();
2040        let mut kept = Vec::with_capacity(rows.len());
2041        for r in rows {
2042            // Only TRUE keeps a row. UNKNOWN excludes it, which is what makes
2043            // `WHERE n.nspname <> 'x'` drop a LEFT JOIN's unmatched rows the
2044            // way Postgres does.
2045            if truthy(&eval(pred, &bind(&r))?) == Some(true) {
2046                kept.push(r);
2047            }
2048        }
2049        rows = kept;
2050        plan.push(Stage::Filter { in_rows, out_rows: rows.len() });
2051    }
2052
2053    // ── 3. the output shape ─────────────────────────────────────────────────
2054    // Resolved from the FIRST row when the select list contains a `*`,
2055    // because only a row knows what columns a schemaless source has. With no
2056    // rows at all a `*` yields no columns, which is the honest answer.
2057    //
2058    // `spans` records which output columns each select ITEM owns, so the
2059    // projection below never has to guess. The previous version walked a
2060    // single counter through both stages, and a `*` that skipped an
2061    // already-named column left the counter pointing at the wrong name — a
2062    // drift that happened to be masked by a fallback.
2063    let mut cols: Vec<OutCol> = vec![];
2064    let mut spans: Vec<(usize, usize)> = Vec::with_capacity(sel.items.len());
2065    for item in &sel.items {
2066        let start = cols.len();
2067        match &item.expr {
2068            Expr::Star => {
2069                if let Some(first) = rows.first() {
2070                    for (n, _) in bind(first).flatten() {
2071                        // A star never emits the same column twice.
2072                        if !cols.iter().any(|c| c.name == n) {
2073                            cols.push(OutCol { key: n.clone(), name: n });
2074                        }
2075                    }
2076                }
2077            }
2078            Expr::QualifiedStar(q) => {
2079                if let Some(first) = rows.first() {
2080                    for (n, _) in bind(first).flatten_binding(q) {
2081                        if !cols.iter().any(|c| c.name == n) {
2082                            cols.push(OutCol { key: n.clone(), name: n });
2083                        }
2084                    }
2085                }
2086            }
2087            _ => {
2088                let name = item.alias.clone().unwrap_or_else(|| derived_name(&item.expr));
2089                // Postgres permits duplicate output names and clients index
2090                // positionally as well as by name, so a collision is NOT
2091                // renamed — silently renaming a column is worse than a
2092                // duplicate, because generated SQL looks for the name it asked
2093                // for. Only the internal KEY is disambiguated.
2094                let key = unique_key(&cols, &name);
2095                cols.push(OutCol { key, name });
2096            }
2097        }
2098        spans.push((start, cols.len()));
2099    }
2100
2101    // ── 4. project ──────────────────────────────────────────────────────────
2102    // The source row is kept beside each projected row, because ORDER BY may
2103    // sort on an expression over columns that are NOT in the select list.
2104    let mut projected: Vec<(Map<String, Value>, JoinedRow)> = Vec::with_capacity(rows.len());
2105    for r in rows {
2106        let b = bind(&r);
2107        let mut obj = Map::new();
2108        for (i, item) in sel.items.iter().enumerate() {
2109            let (start, end) = spans[i];
2110            match &item.expr {
2111                Expr::Star => {
2112                    for (n, v) in b.flatten() {
2113                        if let Some(c) = cols[start..end].iter().find(|c| c.name == n) {
2114                            obj.entry(c.key.clone()).or_insert(v);
2115                        }
2116                    }
2117                }
2118                Expr::QualifiedStar(q) => {
2119                    for (n, v) in b.flatten_binding(q) {
2120                        if let Some(c) = cols[start..end].iter().find(|c| c.name == n) {
2121                            obj.entry(c.key.clone()).or_insert(v);
2122                        }
2123                    }
2124                }
2125                _ => {
2126                    let v = eval(&item.expr, &b)?;
2127                    if let Some(c) = cols.get(start) {
2128                        obj.insert(c.key.clone(), v);
2129                    }
2130                }
2131            }
2132        }
2133        projected.push((obj, r));
2134    }
2135
2136    plan.push(Stage::Project { columns: cols.len(), out_rows: projected.len() });
2137
2138    // ── 5. DISTINCT ─────────────────────────────────────────────────────────
2139    if sel.distinct {
2140        let in_rows = projected.len();
2141        let mut seen: Vec<String> = vec![];
2142        let mut kept = vec![];
2143        for (obj, src) in projected {
2144            // Keyed on the PROJECTED values in output order, which is what
2145            // DISTINCT means — not on the source rows.
2146            let key = cols
2147                .iter()
2148                .map(|c| format!("{:?}", obj.get(&c.key).unwrap_or(&Value::Null)))
2149                .collect::<Vec<_>>()
2150                .join("\u{1}");
2151            if !seen.contains(&key) {
2152                seen.push(key);
2153                kept.push((obj, src));
2154            }
2155        }
2156        projected = kept;
2157        plan.push(Stage::Distinct { in_rows, out_rows: projected.len() });
2158    }
2159
2160    // ── 6. ORDER BY ─────────────────────────────────────────────────────────
2161    if !sel.order_by.is_empty() {
2162        // Sort keys are precomputed so the comparator cannot fail halfway
2163        // through a sort — an error raised inside `sort_by` would leave the
2164        // rows in an arbitrary order and still return them.
2165        let mut keyed: Vec<(Vec<Value>, (Map<String, Value>, JoinedRow))> = vec![];
2166        for (obj, src) in projected {
2167            let mut key = vec![];
2168            for ob in &sel.order_by {
2169                let v = match (ob.ordinal, &ob.expr) {
2170                    (Some(n), _) => {
2171                        let c = cols.get(n - 1).ok_or_else(|| {
2172                            anyhow::anyhow!(
2173                                "ORDER BY {} is out of range: the select list has {} \
2174                                 column(s)", n, cols.len())
2175                        })?;
2176                        obj.get(&c.key).cloned().unwrap_or(Value::Null)
2177                    }
2178                    (None, Some(e)) => {
2179                        // An ORDER BY expression may name a column that is not
2180                        // in the select list, so it is evaluated against the
2181                        // SOURCE row.
2182                        eval(e, &bind(&src))?
2183                    }
2184                    (None, None) => Value::Null,
2185                };
2186                key.push(v);
2187            }
2188            keyed.push((key, (obj, src)));
2189        }
2190
2191        keyed.sort_by(|a, b| {
2192            for (i, ob) in sel.order_by.iter().enumerate() {
2193                let (x, y) = (&a.0[i], &b.0[i]);
2194                let ord = match (x.is_null(), y.is_null()) {
2195                    (true, true) => std::cmp::Ordering::Equal,
2196                    // NULL placement is a direction-independent choice, so it
2197                    // is applied BEFORE the DESC reversal rather than being
2198                    // flipped by it.
2199                    (true, false) => {
2200                        return if ob.nulls_first {
2201                            std::cmp::Ordering::Less
2202                        } else {
2203                            std::cmp::Ordering::Greater
2204                        }
2205                    }
2206                    (false, true) => {
2207                        return if ob.nulls_first {
2208                            std::cmp::Ordering::Greater
2209                        } else {
2210                            std::cmp::Ordering::Less
2211                        }
2212                    }
2213                    (false, false) => cmp_values(x, y).unwrap_or(std::cmp::Ordering::Equal),
2214                };
2215                let ord = if matches!(ob.dir, Dir::Desc) { ord.reverse() } else { ord };
2216                if !ord.is_eq() {
2217                    return ord;
2218                }
2219            }
2220            std::cmp::Ordering::Equal
2221        });
2222
2223        projected = keyed.into_iter().map(|(_, row)| row).collect();
2224        plan.push(Stage::Sort { keys: sel.order_by.len(), rows: projected.len() });
2225    }
2226
2227    // ── 7. OFFSET / LIMIT ───────────────────────────────────────────────────
2228    let mut out: Vec<Value> = projected
2229        .into_iter()
2230        .map(|(obj, _)| Value::Object(obj))
2231        .collect();
2232    let in_rows = out.len();
2233    if let Some(off) = sel.offset {
2234        out = if off >= out.len() { vec![] } else { out.split_off(off) };
2235    }
2236    if let Some(lim) = sel.limit {
2237        out.truncate(lim);
2238    }
2239    if sel.limit.is_some() || sel.offset.is_some() {
2240        plan.push(Stage::Limit {
2241            limit: sel.limit,
2242            offset: sel.offset,
2243            in_rows,
2244            out_rows: out.len(),
2245        });
2246    }
2247
2248    Ok((cols, out, plan))
2249}
2250
2251// ─────────────────────────────────────────────────────────────────────────────
2252// The two join implementations
2253// ─────────────────────────────────────────────────────────────────────────────
2254
2255/// Apply the post-join filter to one produced row.
2256///
2257/// # An `ON` predicate and a post-join `WHERE` predicate are NOT the same thing
2258///
2259/// The physical join evaluates both inside one loop, which is where the
2260/// performance comes from. It does NOT merge them, and the difference is
2261/// semantic law rather than a matter of taste:
2262///
2263/// ```text
2264///   LEFT JOIN ... ON a.x = b.x AND b.tag = 'q'     keeps every left row
2265///   LEFT JOIN ... ON a.x = b.x WHERE b.tag = 'q'   discards the outer rows
2266/// ```
2267///
2268/// So the order is fixed and each step sees only what it should:
2269///
2270/// 1. form the candidate pair
2271/// 2. evaluate `ON` — and this ALONE decides whether the row counts as
2272///    matched, for both the left row and the right row
2273/// 3. synthesise NULLs if the outer join requires it
2274/// 4. evaluate the post-join filter
2275/// 5. count the survivor toward the row budget
2276///
2277/// Step 2 is the load-bearing one. If the filter were allowed to influence
2278/// "matched", a left row whose only partner fails the filter would be
2279/// NULL-extended — and a filter like `WHERE b.tag IS NULL` would then ACCEPT
2280/// that synthesised row, inventing output that the unfused pipeline never
2281/// produces. It is the same trap that made the first predicate-pushdown
2282/// attempt wrong, in a different place.
2283fn keep_row(cand: &JoinedRow, post: Option<&Expr>, removed: &mut usize) -> Result<bool> {
2284    let Some(p) = post else { return Ok(true) };
2285    // Only TRUE keeps a row, exactly as a standalone `WHERE` stage does.
2286    if truthy(&eval(p, &bind(cand))?) == Some(true) {
2287        Ok(true)
2288    } else {
2289        *removed += 1;
2290        Ok(false)
2291    }
2292}
2293
2294/// `RIGHT`/`FULL`: every right row that found no partner survives, with every
2295/// left binding NULL.
2296///
2297/// Shared by both strategies so the two cannot drift apart on the subtlest
2298/// part of outer-join semantics.
2299fn emit_unmatched_right(
2300    out: &mut Vec<JoinedRow>,
2301    kind: JoinKind,
2302    left_bindings: &[String],
2303    right_rows: &[Value],
2304    right_matched: &[bool],
2305    rb: &str,
2306    post: Option<&Expr>,
2307    removed: &mut usize,
2308) -> Result<()> {
2309    if !matches!(kind, JoinKind::Right | JoinKind::Full) {
2310        return Ok(());
2311    }
2312    for (ri, right) in right_rows.iter().enumerate() {
2313        if right_matched[ri] {
2314            continue;
2315        }
2316        let mut cand: JoinedRow = left_bindings.iter().map(|b| (b.clone(), None)).collect();
2317        cand.push((rb.to_string(), Some(right.clone())));
2318        // Outer rows face the post-join filter too — it is a `WHERE`, and a
2319        // `WHERE` applies to every row the join produced.
2320        if keep_row(&cand, post, removed)? {
2321            out.push(cand);
2322        }
2323    }
2324    Ok(())
2325}
2326
2327/// The reference strategy: consider every pair.
2328///
2329/// Quadratic, and kept forever anyway. It is the semantic fallback for
2330/// predicates the hash path cannot key on, the implementation of record for
2331/// non-equality joins, and the oracle the differential tests compare against.
2332fn join_nested_loop(
2333    left_src: &mut dyn LeftSource,
2334    left_bindings: &[String],
2335    join: &Join,
2336    right_rows: &[Value],
2337    rb: &str,
2338    budget: Option<usize>,
2339    post: Option<&Expr>,
2340) -> Result<(Vec<JoinedRow>, usize, usize)> {
2341    let mut out: Vec<JoinedRow> = vec![];
2342    let mut removed = 0usize;
2343    // Which right rows found a partner — only needed for RIGHT and FULL.
2344    let mut right_matched = vec![false; right_rows.len()];
2345
2346    let mut consumed = 0usize;
2347    while let Some(left) = {
2348        if budget.is_some_and(|b| out.len() >= b) {
2349            // Stop ASKING. With a streaming left side this is what keeps the
2350            // source from producing rows nobody will look at.
2351            None
2352        } else {
2353            left_src.next_left()?
2354        }
2355    } {
2356        consumed += 1;
2357        let left = &left;
2358        // Decided by the ON clause ALONE. See `keep_row` for why the
2359        // post-join filter must not touch this.
2360        let mut matched = false;
2361        for (ri, right) in right_rows.iter().enumerate() {
2362            let mut cand: JoinedRow = left.clone();
2363            cand.push((rb.to_string(), Some(right.clone())));
2364            let joins_here = match &join.on {
2365                // CROSS JOIN has no predicate: every pair survives.
2366                None => true,
2367                // An ON that evaluates to UNKNOWN does NOT join, exactly
2368                // as in SQL. Treating UNKNOWN as a match would invent
2369                // pairings out of missing data.
2370                Some(on) => truthy(&eval(on, &bind(&cand))?) == Some(true),
2371            };
2372            if joins_here {
2373                matched = true;
2374                right_matched[ri] = true;
2375                if keep_row(&cand, post, &mut removed)? {
2376                    out.push(cand);
2377                }
2378            }
2379        }
2380        // LEFT/FULL: an unmatched left row survives with a NULL right.
2381        if !matched && matches!(join.kind, JoinKind::Left | JoinKind::Full) {
2382            let mut cand: JoinedRow = left.clone();
2383            cand.push((rb.to_string(), None));
2384            if keep_row(&cand, post, &mut removed)? {
2385                out.push(cand);
2386            }
2387        }
2388    }
2389
2390    // Right-outer rows are appended AFTER every left row, so once the budget
2391    // is met they sit beyond the prefix `LIMIT` will keep and cannot affect the
2392    // answer. Skipping them is the point of the budget; emitting them would be
2393    // correct but pointless work.
2394    if !budget.is_some_and(|b| out.len() >= b) {
2395        emit_unmatched_right(
2396            &mut out, join.kind, left_bindings, right_rows, &right_matched, rb, post,
2397            &mut removed,
2398        )?;
2399    }
2400    Ok((out, removed, consumed))
2401}
2402
2403/// The fast strategy: bucket the right relation, probe it with the left.
2404///
2405/// The hash table is used ONLY to narrow the candidate set. Every surviving
2406/// pair is then evaluated against the complete, unmodified `ON` expression —
2407/// the same call the nested loop makes — so the two strategies answer with the
2408/// same expression evaluated on the same rows. See [`crate::sqljoin`] for why
2409/// bucketing alone would be unsound here.
2410fn join_hash(
2411    left_src: &mut dyn LeftSource,
2412    left_bindings: &[String],
2413    join: &Join,
2414    right_rows: &[Value],
2415    rb: &str,
2416    keys: &[(Expr, Expr)],
2417    budget: Option<usize>,
2418    post: Option<&Expr>,
2419) -> Result<(Vec<JoinedRow>, usize, usize)> {
2420    debug_assert!(!keys.is_empty(), "the planner must not choose Hash with no keys");
2421
2422    // ── build: the right relation, keyed ────────────────────────────────────
2423    let side = sqljoin::HashSide::build(right_rows.len(), |i| {
2424        // A right key reads only the right binding — that is what the planner
2425        // proved — so binding the row alone is sufficient and correct.
2426        let one: JoinedRow = vec![(rb.to_string(), Some(right_rows[i].clone()))];
2427        let b = bind(&one);
2428        let mut k = Vec::with_capacity(keys.len());
2429        for (_, right_expr) in keys {
2430            match sqljoin::hkey(&eval(right_expr, &b)?) {
2431                Some(h) => k.push(h),
2432                // A NULL anywhere in the key means this row joins nothing.
2433                None => return Ok(None),
2434            }
2435        }
2436        Ok(Some(k))
2437    })?;
2438
2439    // ── probe: the accumulated left rows ────────────────────────────────────
2440    let mut out: Vec<JoinedRow> = vec![];
2441    let mut removed = 0usize;
2442    let mut right_matched = vec![false; right_rows.len()];
2443
2444    let mut consumed = 0usize;
2445    while let Some(left) = {
2446        if budget.is_some_and(|b| out.len() >= b) {
2447            None
2448        } else {
2449            left_src.next_left()?
2450        }
2451    } {
2452        consumed += 1;
2453        let left = &left;
2454        let lb = bind(left);
2455        let mut lk = Vec::with_capacity(keys.len());
2456        let mut null_key = false;
2457        for (left_expr, _) in keys {
2458            match sqljoin::hkey(&eval(left_expr, &lb)?) {
2459                Some(h) => lk.push(h),
2460                None => {
2461                    null_key = true;
2462                    break;
2463                }
2464            }
2465        }
2466
2467        let mut matched = false;
2468        // A NULL key matches nothing, so the bucket is not consulted. A
2469        // shortcut, not a safeguard: the confirm step below would reject
2470        // those pairs anyway, since `NULL = NULL` is UNKNOWN.
2471        if !null_key {
2472            for &ri in side.probe(&lk) {
2473                let mut cand: JoinedRow = left.clone();
2474                cand.push((rb.to_string(), Some(right_rows[ri].clone())));
2475                // Confirm. The bucket only suggested this pair.
2476                let joins_here = match &join.on {
2477                    None => true,
2478                    Some(on) => truthy(&eval(on, &bind(&cand))?) == Some(true),
2479                };
2480                if joins_here {
2481                    matched = true;
2482                    right_matched[ri] = true;
2483                    if keep_row(&cand, post, &mut removed)? {
2484                        out.push(cand);
2485                    }
2486                }
2487            }
2488        }
2489        if !matched && matches!(join.kind, JoinKind::Left | JoinKind::Full) {
2490            let mut cand: JoinedRow = left.clone();
2491            cand.push((rb.to_string(), None));
2492            if keep_row(&cand, post, &mut removed)? {
2493                out.push(cand);
2494            }
2495        }
2496    }
2497
2498    // See the note in `join_nested_loop`: beyond the budget these rows cannot
2499    // survive the `LIMIT` prefix.
2500    if !budget.is_some_and(|b| out.len() >= b) {
2501        emit_unmatched_right(
2502            &mut out, join.kind, left_bindings, right_rows, &right_matched, rb, post,
2503            &mut removed,
2504        )?;
2505    }
2506    Ok((out, removed, consumed))
2507}
2508
2509/// Apply the pushed conjuncts for one relation, before it reaches the join.
2510///
2511/// Evaluated against the relation's own binding alone, which is exactly what
2512/// the planner proved is sufficient: a pushed conjunct references only this
2513/// relation, so binding it alone gives the same answer the post-join `WHERE`
2514/// will give for the same row.
2515fn prefilter(
2516    rows: Vec<Value>,
2517    binding: &str,
2518    push: &Pushdown,
2519    plan: &mut Plan,
2520) -> Result<Vec<Value>> {
2521    let Some(preds) = push.for_binding(binding) else { return Ok(rows) };
2522    if preds.is_empty() {
2523        return Ok(rows);
2524    }
2525    let in_rows = rows.len();
2526    let mut kept = Vec::with_capacity(rows.len());
2527    for row in rows {
2528        let one: JoinedRow = vec![(binding.to_string(), Some(row))];
2529        let b = bind(&one);
2530        let mut keep = true;
2531        for p in preds {
2532            // Only TRUE keeps a row, exactly as in `WHERE`. Treating UNKNOWN
2533            // as a keep would make the pre-filter weaker than the filter it
2534            // duplicates, which is harmless; treating it as a drop when the
2535            // real filter would keep it would not be — so the two must agree,
2536            // and they do because this is the same evaluator call.
2537            if truthy(&eval(p, &b)?) != Some(true) {
2538                keep = false;
2539                break;
2540            }
2541        }
2542        if keep {
2543            // Unwrap the row back out of the single-binding wrapper.
2544            if let Some((_, Some(v))) = one.into_iter().next() {
2545                kept.push(v);
2546            }
2547        }
2548    }
2549    plan.push(Stage::Prefilter {
2550        binding: binding.to_string(),
2551        predicates: preds.len(),
2552        in_rows,
2553        out_rows: kept.len(),
2554    });
2555    Ok(kept)
2556}
2557
2558fn fetch(name: &str, resolve: &Resolver) -> Result<Box<dyn Relation>> {
2559    match resolve(name)? {
2560        Some(rel) => Ok(rel),
2561        // Named rather than silently empty: an unknown table that answered
2562        // with no rows would look exactly like an empty one.
2563        None => bail!("relation {:?} does not exist", name),
2564    }
2565}
2566
2567/// Where a join reads its LEFT rows from.
2568///
2569/// Both strategies consume the left side in a SINGLE forward pass — the
2570/// nested loop iterates it once, and the hash join probes with it once — so an
2571/// iterator is a natural fit and no rewinding is needed. That is what makes
2572/// the driving relation streamable while the inner side stays materialised.
2573trait LeftSource {
2574    fn next_left(&mut self) -> Result<Option<JoinedRow>>;
2575    /// Best guess at the row count, for the strategy planner.
2576    fn hint(&self) -> Option<usize>;
2577    /// Whatever rows remain, for a query with no join at all.
2578    fn take_rows(&mut self) -> Vec<JoinedRow>;
2579    /// `(pulled, kept)` when this is a streamed base relation.
2580    fn stats(&self) -> Option<(usize, usize)> {
2581        None
2582    }
2583}
2584
2585/// The driving relation, pulled on demand and pre-filtered inline.
2586///
2587/// Pulling lazily is the whole point: with a row budget, a `LIMIT 20` over a
2588/// join stops asking for rows long before the source is exhausted, so the
2589/// source never has to produce the rest.
2590struct StreamLeft {
2591    rel: Box<dyn Relation>,
2592    binding: String,
2593    preds: Vec<Expr>,
2594    /// Rows actually requested from the source. Reported as the scan's
2595    /// `actual rows`, which for a streamed relation is the honest number —
2596    /// the total is not merely unknown, it is irrelevant to what happened.
2597    pulled: usize,
2598    kept: usize,
2599}
2600
2601impl LeftSource for StreamLeft {
2602    fn next_left(&mut self) -> Result<Option<JoinedRow>> {
2603        while let Some(row) = self.rel.next_row()? {
2604            self.pulled += 1;
2605            let one: JoinedRow = vec![(self.binding.clone(), Some(row))];
2606            if !self.preds.is_empty() {
2607                let b = bind(&one);
2608                let mut keep = true;
2609                for p in &self.preds {
2610                    if truthy(&eval(p, &b)?) != Some(true) {
2611                        keep = false;
2612                        break;
2613                    }
2614                }
2615                if !keep {
2616                    continue;
2617                }
2618            }
2619            self.kept += 1;
2620            return Ok(Some(one));
2621        }
2622        Ok(None)
2623    }
2624    fn hint(&self) -> Option<usize> {
2625        // The source's own count, BEFORE the inline pre-filter. An
2626        // over-estimate, which only ever biases the planner toward the hash
2627        // path — and the two paths are proven equivalent, so a biased choice
2628        // costs time at worst and never correctness.
2629        self.rel.size_hint()
2630    }
2631    fn take_rows(&mut self) -> Vec<JoinedRow> {
2632        // Only reached when there is no join, and the base is materialised in
2633        // that case, so this drains what is left for completeness.
2634        let mut out = vec![];
2635        while let Ok(Some(r)) = self.next_left() {
2636            out.push(r);
2637        }
2638        out
2639    }
2640    fn stats(&self) -> Option<(usize, usize)> {
2641        Some((self.pulled, self.kept))
2642    }
2643}
2644
2645/// An already-materialised left side: the output of a previous join, or a
2646/// base relation in a query the streaming path does not cover.
2647struct VecLeft {
2648    rows: Vec<JoinedRow>,
2649    at: usize,
2650}
2651
2652impl LeftSource for VecLeft {
2653    fn next_left(&mut self) -> Result<Option<JoinedRow>> {
2654        let r = self.rows.get(self.at).cloned();
2655        if r.is_some() {
2656            self.at += 1;
2657        }
2658        Ok(r)
2659    }
2660    fn hint(&self) -> Option<usize> {
2661        Some(self.rows.len().saturating_sub(self.at))
2662    }
2663    fn take_rows(&mut self) -> Vec<JoinedRow> {
2664        let mut v = std::mem::take(&mut self.rows);
2665        if self.at > 0 {
2666            v = v.split_off(self.at);
2667        }
2668        self.at = 0;
2669        v
2670    }
2671}
2672
2673/// Pull a relation completely into memory.
2674///
2675/// Used for the INNER side of a join, which genuinely has to be whole: a hash
2676/// join must build its table before probing, and a nested loop re-scans it for
2677/// every left row. Streaming it would save nothing, so this says plainly that
2678/// it is being materialised on purpose rather than by omission.
2679fn drain(mut rel: Box<dyn Relation>) -> Result<Vec<Value>> {
2680    let mut out = Vec::with_capacity(rel.size_hint().unwrap_or(0));
2681    while let Some(row) = rel.next_row()? {
2682        out.push(row);
2683    }
2684    Ok(out)
2685}
2686
2687/// Parse and run in one call.
2688pub fn run(sql: &str, resolve: &Resolver) -> Result<(Vec<OutCol>, Vec<Value>)> {
2689    let sel = parse(sql)?;
2690    execute(&sel, resolve)
2691}
2692
2693#[cfg(test)]
2694mod lexer_tests {
2695    use super::*;
2696
2697    fn kinds(src: &str) -> Vec<Tok> {
2698        let mut t = lex(src).expect("lexes");
2699        t.pop(); // drop Eof
2700        t
2701    }
2702
2703    #[test]
2704    fn a_word_keeps_both_its_canonical_and_raw_spelling() {
2705        // A column may legitimately be called `count` or `value`; folding case
2706        // in the lexer would later look up a key the data does not have.
2707        assert_eq!(
2708            kinds("Select"),
2709            vec![Tok::Word { upper: "SELECT".into(), raw: "Select".into() }]
2710        );
2711    }
2712
2713    #[test]
2714    fn a_quoted_identifier_is_never_a_keyword() {
2715        assert_eq!(kinds(r#""select""#), vec![Tok::Quoted("select".into())]);
2716        // …and keeps its case, which is the whole point of quoting it.
2717        assert_eq!(kinds(r#""Name""#), vec![Tok::Quoted("Name".into())]);
2718    }
2719
2720    #[test]
2721    fn a_doubled_quote_is_one_literal_quote() {
2722        assert_eq!(kinds("'it''s'"), vec![Tok::Str("it's".into())]);
2723        assert_eq!(kinds(r#""a""b""#), vec![Tok::Quoted("a\"b".into())]);
2724    }
2725
2726    #[test]
2727    fn an_E_string_decodes_the_escapes_catalogue_sql_uses() {
2728        // `array_to_string(d.datacl, E'\n')` appears verbatim in psql's \l.
2729        assert_eq!(kinds(r"E'\n'"), vec![Tok::Str("\n".into())]);
2730        assert_eq!(kinds(r"E'a\tb'"), vec![Tok::Str("a\tb".into())]);
2731        // An unknown escape keeps its character rather than vanishing.
2732        assert_eq!(kinds(r"E'\q'"), vec![Tok::Str("q".into())]);
2733    }
2734
2735    #[test]
2736    fn operators_match_longest_first() {
2737        // Order is load-bearing: `!~*` must not tokenise as `!~` plus `*`.
2738        assert_eq!(kinds("!~*"), vec![Tok::Op("!~*".into())]);
2739        assert_eq!(kinds("!~"), vec![Tok::Op("!~".into())]);
2740        assert_eq!(kinds("~*"), vec![Tok::Op("~*".into())]);
2741        assert_eq!(kinds("<>"), vec![Tok::Op("<>".into())]);
2742        assert_eq!(kinds("!="), vec![Tok::Op("!=".into())]);
2743        assert_eq!(kinds(">="), vec![Tok::Op(">=".into())]);
2744        assert_eq!(kinds("::"), vec![Tok::Op("::".into())]);
2745        assert_eq!(kinds("||"), vec![Tok::Op("||".into())]);
2746        assert_eq!(kinds("~"), vec![Tok::Op("~".into())]);
2747    }
2748
2749    #[test]
2750    fn comments_are_skipped_including_nested_block_comments() {
2751        assert_eq!(kinds("1 -- trailing\n"), vec![Tok::Num(1.0)]);
2752        assert_eq!(kinds("1 /* a */ 2"), vec![Tok::Num(1.0), Tok::Num(2.0)]);
2753        // SQL block comments nest, unlike C's.
2754        assert_eq!(kinds("1 /* a /* b */ c */ 2"), vec![Tok::Num(1.0), Tok::Num(2.0)]);
2755        assert!(lex("1 /* unterminated").is_err());
2756    }
2757
2758    #[test]
2759    fn numbers_parse_including_fractions_and_exponents() {
2760        assert_eq!(kinds("42"), vec![Tok::Num(42.0)]);
2761        assert_eq!(kinds("4.5"), vec![Tok::Num(4.5)]);
2762        assert_eq!(kinds(".5"), vec![Tok::Num(0.5)]);
2763        assert_eq!(kinds("1e3"), vec![Tok::Num(1000.0)]);
2764        assert_eq!(kinds("1e-2"), vec![Tok::Num(0.01)]);
2765        // `1e` is the number 1 followed by an identifier, not a broken number.
2766        assert_eq!(
2767            kinds("1e"),
2768            vec![Tok::Num(1.0), Tok::Word { upper: "E".into(), raw: "e".into() }]
2769        );
2770    }
2771
2772    #[test]
2773    fn an_unterminated_literal_is_an_error_not_a_truncation() {
2774        assert!(lex("'abc").is_err());
2775        assert!(lex(r#""abc"#).is_err());
2776    }
2777
2778    #[test]
2779    fn an_unknown_character_is_REFUSED_rather_than_skipped() {
2780        // Skipping is how a parser silently reads a different query than the
2781        // one it was handed.
2782        let e = lex("SELECT 1 @ 2").unwrap_err().to_string();
2783        assert!(e.contains('@'), "{}", e);
2784    }
2785
2786    #[test]
2787    fn the_real_dn_query_lexes() {
2788        let sql = r#"SELECT n.nspname AS "Name",
2789          pg_catalog.pg_get_userbyid(n.nspowner) AS "Owner"
2790        FROM pg_catalog.pg_namespace n
2791        WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'
2792        ORDER BY 1;"#;
2793        let toks = lex(sql).expect("psql's \\dn must lex");
2794        assert!(toks.contains(&Tok::Quoted("Name".into())));
2795        assert!(toks.contains(&Tok::Op("!~".into())));
2796        assert!(toks.contains(&Tok::Op("<>".into())));
2797        assert!(toks.contains(&Tok::Str("^pg_".into())));
2798    }
2799
2800    #[test]
2801    fn the_real_dt_query_lexes() {
2802        let sql = r#"SELECT n.nspname as "Schema", c.relname as "Name",
2803          CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' END as "Type",
2804          pg_catalog.pg_get_userbyid(c.relowner) as "Owner"
2805        FROM pg_catalog.pg_class c
2806             LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
2807             LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam
2808        WHERE c.relkind IN ('r','p','')
2809              AND n.nspname <> 'pg_catalog'
2810              AND n.nspname !~ '^pg_toast'
2811          AND pg_catalog.pg_table_is_visible(c.oid)
2812        ORDER BY 1,2;"#;
2813        let toks = lex(sql).expect("psql's \\dt must lex");
2814        assert!(toks.iter().any(|t| t.is_kw("CASE")));
2815        assert!(toks.iter().any(|t| t.is_kw("LEFT")));
2816        assert!(toks.iter().any(|t| t.is_kw("JOIN")));
2817        // The empty string in `IN ('r','p','')` must survive as a real value.
2818        assert!(toks.contains(&Tok::Str(String::new())));
2819    }
2820}
2821
2822#[cfg(test)]
2823mod parser_tests {
2824    use super::*;
2825    use serde_json::json;
2826
2827    fn col(qual: Option<&str>, name: &str) -> Expr {
2828        Expr::Column { qual: qual.map(str::to_string), name: name.to_string() }
2829    }
2830
2831    #[test]
2832    fn a_bare_select_list_and_from() {
2833        let s = parse("SELECT a, b FROM t").unwrap();
2834        assert_eq!(s.items.len(), 2);
2835        assert_eq!(s.items[0].expr, col(None, "a"));
2836        assert_eq!(s.from.unwrap().name, "t");
2837    }
2838
2839    #[test]
2840    fn a_clause_keyword_is_never_read_as_a_bare_alias() {
2841        // Without the guard, `FROM t WHERE x = 1` parses `t` aliased as
2842        // `WHERE`, the predicate vanishes, and EVERY row comes back — a
2843        // silently wrong answer of the worst kind.
2844        let s = parse("SELECT a FROM t WHERE a = 1").unwrap();
2845        assert_eq!(s.from.clone().unwrap().alias, None);
2846        assert!(s.where_.is_some(), "the WHERE clause must survive");
2847        let s = parse("SELECT a FROM t ORDER BY a").unwrap();
2848        assert_eq!(s.from.unwrap().alias, None);
2849        assert_eq!(s.order_by.len(), 1);
2850    }
2851
2852    #[test]
2853    fn a_real_alias_is_kept_in_both_spellings() {
2854        assert_eq!(parse("SELECT a FROM t x").unwrap().from.unwrap().alias,
2855                   Some("x".to_string()));
2856        assert_eq!(parse("SELECT a FROM t AS x").unwrap().from.unwrap().alias,
2857                   Some("x".to_string()));
2858    }
2859
2860    #[test]
2861    fn a_tables_binding_is_its_alias_else_its_bare_name() {
2862        let t = TableRef { name: "pg_catalog.pg_class".into(), alias: Some("c".into()) };
2863        assert_eq!(t.binding(), "c");
2864        let t = TableRef { name: "pg_catalog.pg_class".into(), alias: None };
2865        assert_eq!(t.binding(), "pg_class", "the schema is not how a column is addressed");
2866    }
2867
2868    #[test]
2869    fn a_qualified_column_keeps_only_its_immediate_qualifier() {
2870        assert_eq!(parse("SELECT n.nspname FROM x").unwrap().items[0].expr,
2871                   col(Some("n"), "nspname"));
2872        // In `public.orders.id` the binding is `orders`; the schema is not
2873        // part of how a column is addressed.
2874        assert_eq!(parse("SELECT public.orders.id FROM x").unwrap().items[0].expr,
2875                   col(Some("orders"), "id"));
2876    }
2877
2878    #[test]
2879    fn an_alias_may_be_a_quoted_string_with_significant_case() {
2880        let s = parse(r#"SELECT n.nspname AS "Name" FROM x"#).unwrap();
2881        assert_eq!(s.items[0].alias, Some("Name".to_string()));
2882    }
2883
2884    #[test]
2885    fn a_schema_qualified_function_drops_its_schema() {
2886        // `pg_catalog.pg_get_userbyid` is the same function as
2887        // `pg_get_userbyid`; the schema is not part of its identity here.
2888        let s = parse("SELECT pg_catalog.pg_get_userbyid(n.nspowner) FROM x").unwrap();
2889        match &s.items[0].expr {
2890            Expr::Func { name, args } => {
2891                assert_eq!(name, "pg_get_userbyid");
2892                assert_eq!(args.len(), 1);
2893                assert_eq!(args[0], col(Some("n"), "nspowner"));
2894            }
2895            other => panic!("{:?}", other),
2896        }
2897    }
2898
2899    #[test]
2900    fn operator_precedence_matches_sql() {
2901        // AND binds tighter than OR: `a OR b AND c` is `a OR (b AND c)`.
2902        // Getting this backwards silently returns the wrong rows.
2903        let s = parse("SELECT 1 FROM t WHERE a = 1 OR b = 2 AND c = 3").unwrap();
2904        match s.where_.unwrap() {
2905            Expr::Binary { op, right, .. } => {
2906                assert_eq!(op, "OR");
2907                assert!(matches!(*right, Expr::Binary { ref op, .. } if op == "AND"),
2908                        "AND must bind tighter than OR");
2909            }
2910            other => panic!("{:?}", other),
2911        }
2912        // Comparison binds tighter than AND.
2913        let s = parse("SELECT 1 FROM t WHERE a = 1 AND b = 2").unwrap();
2914        assert!(matches!(s.where_.unwrap(), Expr::Binary { ref op, .. } if op == "AND"));
2915        // Multiplication binds tighter than addition.
2916        let s = parse("SELECT 1 + 2 * 3 FROM t").unwrap();
2917        match &s.items[0].expr {
2918            Expr::Binary { op, right, .. } => {
2919                assert_eq!(op, "+");
2920                assert!(matches!(**right, Expr::Binary { ref op, .. } if op == "*"));
2921            }
2922            other => panic!("{:?}", other),
2923        }
2924    }
2925
2926    #[test]
2927    fn parentheses_override_precedence() {
2928        let s = parse("SELECT 1 FROM t WHERE (a = 1 OR b = 2) AND c = 3").unwrap();
2929        match s.where_.unwrap() {
2930            Expr::Binary { op, left, .. } => {
2931                assert_eq!(op, "AND");
2932                assert!(matches!(*left, Expr::Binary { ref op, .. } if op == "OR"));
2933            }
2934            other => panic!("{:?}", other),
2935        }
2936    }
2937
2938    #[test]
2939    fn in_and_is_null_and_between_parse_in_both_polarities() {
2940        let s = parse("SELECT 1 FROM t WHERE k IN ('r','p','')").unwrap();
2941        match s.where_.unwrap() {
2942            Expr::InList { list, negated, .. } => {
2943                assert_eq!(list.len(), 3);
2944                assert!(!negated);
2945                // The empty string in psql's `IN ('r','p','')` is a REAL value.
2946                assert_eq!(list[2], Expr::Literal(json!("")));
2947            }
2948            other => panic!("{:?}", other),
2949        }
2950        assert!(matches!(parse("SELECT 1 FROM t WHERE k NOT IN (1)").unwrap().where_.unwrap(),
2951                         Expr::InList { negated: true, .. }));
2952        assert!(matches!(parse("SELECT 1 FROM t WHERE k IS NULL").unwrap().where_.unwrap(),
2953                         Expr::IsNull { negated: false, .. }));
2954        assert!(matches!(parse("SELECT 1 FROM t WHERE k IS NOT NULL").unwrap().where_.unwrap(),
2955                         Expr::IsNull { negated: true, .. }));
2956        // BETWEEN's bounds must not let AND escape as a boolean operator.
2957        let s = parse("SELECT 1 FROM t WHERE n BETWEEN 1 AND 5").unwrap();
2958        assert!(matches!(s.where_.unwrap(), Expr::Binary { ref op, .. } if op == "AND"));
2959    }
2960
2961    #[test]
2962    fn both_case_spellings_parse() {
2963        // simple CASE — what psql's \dt uses, with nine branches.
2964        let s = parse("SELECT CASE k WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' \
2965                       ELSE 'other' END FROM t").unwrap();
2966        match &s.items[0].expr {
2967            Expr::Case { operand, whens, else_ } => {
2968                assert!(operand.is_some());
2969                assert_eq!(whens.len(), 2);
2970                assert!(else_.is_some());
2971            }
2972            other => panic!("{:?}", other),
2973        }
2974        // searched CASE
2975        let s = parse("SELECT CASE WHEN k = 'r' THEN 1 END FROM t").unwrap();
2976        match &s.items[0].expr {
2977            Expr::Case { operand, whens, else_ } => {
2978                assert!(operand.is_none());
2979                assert_eq!(whens.len(), 1);
2980                assert!(else_.is_none());
2981            }
2982            other => panic!("{:?}", other),
2983        }
2984        // A CASE with no WHEN is malformed and must be refused.
2985        assert!(parse("SELECT CASE k END FROM t").is_err());
2986    }
2987
2988    #[test]
2989    fn every_join_flavour_parses_and_an_inner_join_demands_ON() {
2990        for (sql, kind) in [
2991            ("SELECT 1 FROM a JOIN b ON a.x = b.x", JoinKind::Inner),
2992            ("SELECT 1 FROM a INNER JOIN b ON a.x = b.x", JoinKind::Inner),
2993            ("SELECT 1 FROM a LEFT JOIN b ON a.x = b.x", JoinKind::Left),
2994            ("SELECT 1 FROM a LEFT OUTER JOIN b ON a.x = b.x", JoinKind::Left),
2995            ("SELECT 1 FROM a RIGHT JOIN b ON a.x = b.x", JoinKind::Right),
2996            ("SELECT 1 FROM a FULL OUTER JOIN b ON a.x = b.x", JoinKind::Full),
2997            ("SELECT 1 FROM a CROSS JOIN b", JoinKind::Cross),
2998        ] {
2999            let s = parse(sql).unwrap_or_else(|e| panic!("{}: {}", sql, e));
3000            assert_eq!(s.joins.len(), 1, "{}", sql);
3001            assert_eq!(s.joins[0].kind, kind, "{}", sql);
3002        }
3003        // A comma FROM list is an implicit cross join.
3004        let s = parse("SELECT 1 FROM a, b").unwrap();
3005        assert_eq!(s.joins[0].kind, JoinKind::Cross);
3006        // A join that needs a predicate must not silently become a cross
3007        // product — that turns two tables into n*m confidently wrong rows.
3008        assert!(parse("SELECT 1 FROM a LEFT JOIN b").is_err());
3009        assert!(parse("SELECT 1 FROM a JOIN b USING (x)").is_err());
3010    }
3011
3012    #[test]
3013    fn order_by_reads_a_number_as_an_ORDINAL() {
3014        // psql's \dt ends with `ORDER BY 1,2`. Reading those as the constants
3015        // 1 and 2 sorts every row equally and silently yields an unordered
3016        // listing that looks fine.
3017        let s = parse("SELECT a, b FROM t ORDER BY 1, 2 DESC").unwrap();
3018        assert_eq!(s.order_by.len(), 2);
3019        assert_eq!(s.order_by[0].ordinal, Some(1));
3020        assert_eq!(s.order_by[0].dir, Dir::Asc);
3021        assert_eq!(s.order_by[1].ordinal, Some(2));
3022        assert_eq!(s.order_by[1].dir, Dir::Desc);
3023        // An expression still parses as an expression.
3024        let s = parse("SELECT a FROM t ORDER BY lower(a) ASC").unwrap();
3025        assert!(s.order_by[0].ordinal.is_none());
3026        assert!(s.order_by[0].expr.is_some());
3027    }
3028
3029    #[test]
3030    fn null_ordering_defaults_the_way_postgres_defaults() {
3031        let s = parse("SELECT a FROM t ORDER BY a").unwrap();
3032        assert!(!s.order_by[0].nulls_first, "ASC defaults to NULLS LAST");
3033        let s = parse("SELECT a FROM t ORDER BY a DESC").unwrap();
3034        assert!(s.order_by[0].nulls_first, "DESC defaults to NULLS FIRST");
3035        let s = parse("SELECT a FROM t ORDER BY a NULLS FIRST").unwrap();
3036        assert!(s.order_by[0].nulls_first, "an explicit clause wins");
3037    }
3038
3039    #[test]
3040    fn limit_and_offset_parse_in_either_order() {
3041        let s = parse("SELECT a FROM t LIMIT 5 OFFSET 2").unwrap();
3042        assert_eq!((s.limit, s.offset), (Some(5), Some(2)));
3043        let s = parse("SELECT a FROM t OFFSET 2 LIMIT 5").unwrap();
3044        assert_eq!((s.limit, s.offset), (Some(5), Some(2)));
3045        let s = parse("SELECT a FROM t LIMIT ALL").unwrap();
3046        assert_eq!(s.limit, None);
3047    }
3048
3049    #[test]
3050    fn casts_parse_and_are_recorded_rather_than_rejected() {
3051        // `pr.prattrs::pg_catalog.int2[]` appears verbatim in psql's \d.
3052        let s = parse("SELECT x::int2 FROM t").unwrap();
3053        assert!(matches!(s.items[0].expr, Expr::Cast { .. }));
3054        let s = parse("SELECT x::pg_catalog.int2[] FROM t").unwrap();
3055        match &s.items[0].expr {
3056            Expr::Cast { ty, .. } => assert_eq!(ty, "int2[]"),
3057            other => panic!("{:?}", other),
3058        }
3059    }
3060
3061    #[test]
3062    fn star_and_qualified_star_parse() {
3063        assert_eq!(parse("SELECT * FROM t").unwrap().items[0].expr, Expr::Star);
3064        assert_eq!(parse("SELECT c.* FROM t c").unwrap().items[0].expr,
3065                   Expr::QualifiedStar("c".into()));
3066        match &parse("SELECT count(*) FROM t").unwrap().items[0].expr {
3067            Expr::Func { name, args } => {
3068                assert_eq!(name, "count");
3069                assert_eq!(args, &vec![Expr::Star]);
3070            }
3071            other => panic!("{:?}", other),
3072        }
3073    }
3074
3075    #[test]
3076    fn a_parenthesis_free_function_parses_as_a_zero_arg_call() {
3077        // `current_schema` is legal without parentheses.
3078        match &parse("SELECT current_schema FROM t").unwrap().items[0].expr {
3079            Expr::Func { name, args } => {
3080                assert_eq!(name, "current_schema");
3081                assert!(args.is_empty());
3082            }
3083            other => panic!("{:?}", other),
3084        }
3085    }
3086
3087    #[test]
3088    fn unsupported_clauses_are_refused_by_name() {
3089        for (sql, needle) in [
3090            ("SELECT a FROM t GROUP BY a", "GROUP BY"),
3091            ("SELECT a FROM t HAVING count(*) > 1", "HAVING"),
3092            ("SELECT DISTINCT ON (a) a FROM t", "DISTINCT ON"),
3093        ] {
3094            let e = parse(sql).unwrap_err().to_string();
3095            assert!(e.contains(needle), "{} -> {}", sql, e);
3096        }
3097        // Trailing garbage is an error, not something to ignore.
3098        assert!(parse("SELECT a FROM t JUNK JUNK2").is_err());
3099    }
3100
3101    #[test]
3102    fn THE_dn_QUERY_parses_completely() {
3103        let s = parse(
3104            r#"SELECT n.nspname AS "Name",
3105                 pg_catalog.pg_get_userbyid(n.nspowner) AS "Owner"
3106               FROM pg_catalog.pg_namespace n
3107               WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'
3108               ORDER BY 1;"#,
3109        )
3110        .expect("psql's \\dn must parse");
3111
3112        assert_eq!(s.items.len(), 2);
3113        assert_eq!(s.items[0].alias, Some("Name".into()));
3114        assert_eq!(s.items[1].alias, Some("Owner".into()));
3115        let from = s.from.unwrap();
3116        assert_eq!(from.name, "pg_catalog.pg_namespace");
3117        assert_eq!(from.binding(), "n");
3118        assert!(s.where_.is_some());
3119        assert_eq!(s.order_by[0].ordinal, Some(1));
3120    }
3121
3122    #[test]
3123    fn THE_dt_QUERY_parses_completely() {
3124        let s = parse(
3125            r#"SELECT n.nspname as "Schema",
3126                 c.relname as "Name",
3127                 CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view'
3128                   WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index'
3129                   WHEN 'S' THEN 'sequence' WHEN 't' THEN 'TOAST table'
3130                   WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'partitioned table'
3131                   WHEN 'I' THEN 'partitioned index' END as "Type",
3132                 pg_catalog.pg_get_userbyid(c.relowner) as "Owner"
3133               FROM pg_catalog.pg_class c
3134                    LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
3135                    LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam
3136               WHERE c.relkind IN ('r','p','')
3137                     AND n.nspname <> 'pg_catalog'
3138                     AND n.nspname !~ '^pg_toast'
3139                     AND n.nspname <> 'information_schema'
3140                 AND pg_catalog.pg_table_is_visible(c.oid)
3141               ORDER BY 1,2;"#,
3142        )
3143        .expect("psql's \\dt must parse");
3144
3145        assert_eq!(s.items.len(), 4);
3146        assert_eq!(s.items[2].alias, Some("Type".into()));
3147        match &s.items[2].expr {
3148            Expr::Case { whens, .. } => assert_eq!(whens.len(), 9, "all nine branches"),
3149            other => panic!("{:?}", other),
3150        }
3151        assert_eq!(s.joins.len(), 2);
3152        assert!(s.joins.iter().all(|j| j.kind == JoinKind::Left && j.on.is_some()));
3153        assert_eq!(s.from.unwrap().binding(), "c");
3154        assert_eq!(s.order_by.len(), 2);
3155        assert_eq!(
3156            (s.order_by[0].ordinal, s.order_by[1].ordinal),
3157            (Some(1), Some(2))
3158        );
3159    }
3160}
3161
3162#[cfg(test)]
3163mod eval_tests {
3164    use super::*;
3165    use serde_json::json;
3166
3167    /// One binding named `t` holding `row`.
3168    fn one(row: &Value) -> Bound<'_> {
3169        Bound { parts: vec![("t".to_string(), Some(row))] }
3170    }
3171
3172    fn ev(sql_expr: &str, row: &Value) -> Result<Value> {
3173        let s = parse(&format!("SELECT {} FROM t", sql_expr))?;
3174        eval(&s.items[0].expr, &one(row))
3175    }
3176
3177    fn v(sql_expr: &str, row: &Value) -> Value {
3178        ev(sql_expr, row).unwrap_or_else(|e| panic!("{}: {}", sql_expr, e))
3179    }
3180
3181    #[test]
3182    fn literals_and_columns_resolve() {
3183        let r = json!({"a": 1, "s": "x", "b": true, "n": null});
3184        assert_eq!(v("42", &r), json!(42));
3185        assert_eq!(v("'hi'", &r), json!("hi"));
3186        assert_eq!(v("NULL", &r), Value::Null);
3187        assert_eq!(v("TRUE", &r), json!(true));
3188        assert_eq!(v("a", &r), json!(1));
3189        assert_eq!(v("t.a", &r), json!(1));
3190        assert_eq!(v("s", &r), json!("x"));
3191        // An absent column is NULL, because a schemaless document may omit
3192        // any field — that is data, not an error.
3193        assert_eq!(v("nosuch", &r), Value::Null);
3194    }
3195
3196    #[test]
3197    fn an_unknown_table_ALIAS_is_an_error_while_an_unknown_column_is_null() {
3198        // The distinction matters: a typo'd alias is a query bug worth
3199        // reporting, while a missing field is ordinary schemaless behaviour.
3200        let r = json!({"a": 1});
3201        assert_eq!(v("t.nosuch", &r), Value::Null);
3202        let e = ev("zz.a", &r).unwrap_err().to_string();
3203        assert!(e.contains("zz"), "{}", e);
3204    }
3205
3206    // ── SQL's three-valued logic. The subtle, dangerous part. ───────────────
3207
3208    #[test]
3209    fn every_comparison_over_NULL_is_UNKNOWN_including_null_equals_null() {
3210        let r = json!({"n": null, "a": 1});
3211        assert_eq!(v("n = 1", &r), Value::Null);
3212        assert_eq!(v("n != 1", &r), Value::Null);
3213        assert_eq!(v("n < 1", &r), Value::Null);
3214        // The one everybody gets wrong: NULL = NULL is UNKNOWN, not true.
3215        assert_eq!(v("n = n", &r), Value::Null);
3216        assert_eq!(v("n = NULL", &r), Value::Null);
3217    }
3218
3219    #[test]
3220    fn NOT_UNKNOWN_is_UNKNOWN_not_true() {
3221        // Collapsing UNKNOWN to false here would make
3222        // `WHERE NOT (n.nspname = 'x')` include the unmatched rows of a LEFT
3223        // JOIN that Postgres excludes — the counts would silently disagree.
3224        let r = json!({"n": null});
3225        assert_eq!(v("NOT (n = 1)", &r), Value::Null);
3226        assert_eq!(v("NOT TRUE", &r), json!(false));
3227        assert_eq!(v("NOT FALSE", &r), json!(true));
3228    }
3229
3230    #[test]
3231    fn AND_and_OR_follow_the_three_valued_truth_tables() {
3232        let r = json!({"n": null});
3233        // false AND unknown = FALSE (the false decides it)
3234        assert_eq!(v("FALSE AND n = 1", &r), json!(false));
3235        // true AND unknown = unknown
3236        assert_eq!(v("TRUE AND n = 1", &r), Value::Null);
3237        // true OR unknown = TRUE (the true decides it)
3238        assert_eq!(v("TRUE OR n = 1", &r), json!(true));
3239        // false OR unknown = unknown
3240        assert_eq!(v("FALSE OR n = 1", &r), Value::Null);
3241        // and the ordinary cases
3242        assert_eq!(v("TRUE AND TRUE", &r), json!(true));
3243        assert_eq!(v("TRUE AND FALSE", &r), json!(false));
3244        assert_eq!(v("FALSE OR FALSE", &r), json!(false));
3245    }
3246
3247    #[test]
3248    fn IS_NULL_is_the_one_predicate_that_is_never_unknown() {
3249        let r = json!({"n": null, "a": 1});
3250        assert_eq!(v("n IS NULL", &r), json!(true));
3251        assert_eq!(v("n IS NOT NULL", &r), json!(false));
3252        assert_eq!(v("a IS NULL", &r), json!(false));
3253        assert_eq!(v("a IS NOT NULL", &r), json!(true));
3254        // An absent column is indistinguishable from an explicit null, which
3255        // is the honest answer for a schemaless store.
3256        assert_eq!(v("nosuch IS NULL", &r), json!(true));
3257    }
3258
3259    #[test]
3260    fn NOT_IN_with_a_NULL_in_the_list_is_UNKNOWN_the_classic_trap() {
3261        let r = json!({"a": 2});
3262        assert_eq!(v("a IN (1, 2)", &r), json!(true));
3263        assert_eq!(v("a IN (1, 3)", &r), json!(false));
3264        assert_eq!(v("a NOT IN (1, 3)", &r), json!(true));
3265        // `2 NOT IN (1, NULL)` is UNKNOWN, not true — 2 MIGHT equal the null.
3266        // Postgres agrees, and getting this wrong silently includes rows.
3267        assert_eq!(v("a NOT IN (1, NULL)", &r), Value::Null);
3268        // A match still decides it even with a null present.
3269        assert_eq!(v("a IN (2, NULL)", &r), json!(true));
3270        // NULL on the left is unknown regardless.
3271        assert_eq!(v("nosuch IN (1)", &r), Value::Null);
3272    }
3273
3274    // ── operators ───────────────────────────────────────────────────────────
3275
3276    #[test]
3277    fn comparisons_work_across_numbers_strings_and_booleans() {
3278        let r = json!({"n": 5, "s": "b", "t": true});
3279        assert_eq!(v("n > 3", &r), json!(true));
3280        assert_eq!(v("n <= 5", &r), json!(true));
3281        assert_eq!(v("s < 'c'", &r), json!(true));
3282        assert_eq!(v("s > 'c'", &r), json!(false));
3283        // A number and a numeric-looking string compare NUMERICALLY, because
3284        // a catalogue oid is a number while a client may quote it.
3285        assert_eq!(v("n = '5'", &r), json!(true));
3286        assert_eq!(v("n = '5.0'", &r), json!(true));
3287        // And a non-numeric string falls back to text comparison rather than
3288        // erroring.
3289        assert_eq!(v("n = 'five'", &r), json!(false));
3290    }
3291
3292    #[test]
3293    fn the_regex_operators_use_the_SAME_matcher_as_NQL() {
3294        // Two implementations would be two chances for the SQL surface and
3295        // the NQL surface to disagree about the same operator.
3296        let r = json!({"s": "pg_catalog"});
3297        assert_eq!(v("s ~ '^pg_'", &r), json!(true));
3298        assert_eq!(v("s !~ '^pg_'", &r), json!(false));
3299        assert_eq!(v("s ~ '^PG_'", &r), json!(false));
3300        assert_eq!(v("s ~* '^PG_'", &r), json!(true));
3301        assert_eq!(v("s !~ '^zz'", &r), json!(true));
3302        // NULL propagates.
3303        assert_eq!(v("nosuch ~ '^x'", &r), Value::Null);
3304        // And an unsupported metacharacter is refused, not approximated.
3305        assert!(ev("s ~ 'a+b'", &r).is_err());
3306    }
3307
3308    #[test]
3309    fn like_works_in_all_four_spellings() {
3310        let r = json!({"s": "Acme Pool"});
3311        assert_eq!(v("s LIKE 'Acme%'", &r), json!(true));
3312        assert_eq!(v("s LIKE 'acme%'", &r), json!(false));
3313        assert_eq!(v("s ILIKE 'acme%'", &r), json!(true));
3314        assert_eq!(v("s NOT LIKE 'zz%'", &r), json!(true));
3315        assert_eq!(v("nosuch LIKE 'x'", &r), Value::Null);
3316    }
3317
3318    #[test]
3319    fn arithmetic_and_concatenation_propagate_null_and_refuse_div_by_zero() {
3320        let r = json!({"a": 7, "b": 2});
3321        assert_eq!(v("a + b", &r), json!(9));
3322        assert_eq!(v("a - b", &r), json!(5));
3323        assert_eq!(v("a * b", &r), json!(14));
3324        assert_eq!(v("a / b", &r), json!(3.5));
3325        assert_eq!(v("a % b", &r), json!(1));
3326        assert_eq!(v("-a", &r), json!(-7));
3327        // A non-integral result stays a float — the rule is about how INTEGRAL
3328        // values render, not about collapsing every number to an integer.
3329        assert_eq!(v("a / b", &r), json!(3.5));
3330        assert_eq!(v("b / a", &r), json!(2.0 / 7.0));
3331        assert_eq!(v("'x' || 'y'", &r), json!("xy"));
3332        assert_eq!(v("'x' || nosuch", &r), Value::Null);
3333        assert_eq!(v("a + nosuch", &r), Value::Null);
3334        // Division by zero is an ERROR in Postgres, not infinity. Returning
3335        // inf would be a confidently wrong number.
3336        assert!(ev("a / 0", &r).is_err());
3337        assert!(ev("a % 0", &r).is_err());
3338    }
3339
3340    #[test]
3341    fn a_cast_is_transparent_rather_than_rejected() {
3342        // `pr.prattrs::pg_catalog.int2[]` appears in real catalogue SQL, and
3343        // the cast cannot change the answer for the shapes it is used on.
3344        let r = json!({"a": 7});
3345        assert_eq!(v("a::int2", &r), json!(7));
3346        assert_eq!(v("a::pg_catalog.int2[]", &r), json!(7));
3347    }
3348
3349    // ── CASE ────────────────────────────────────────────────────────────────
3350
3351    #[test]
3352    fn a_simple_CASE_picks_the_matching_branch() {
3353        // This is psql's \dt shape, with the real relkind values.
3354        let expr = "CASE k WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' \
3355                    WHEN 'i' THEN 'index' END";
3356        assert_eq!(v(expr, &json!({"k": "r"})), json!("table"));
3357        assert_eq!(v(expr, &json!({"k": "v"})), json!("view"));
3358        assert_eq!(v(expr, &json!({"k": "i"})), json!("index"));
3359        // No branch and no ELSE is NULL — which is exactly what \dt relies on
3360        // for a relkind it does not name.
3361        assert_eq!(v(expr, &json!({"k": "z"})), Value::Null);
3362    }
3363
3364    #[test]
3365    fn a_searched_CASE_evaluates_predicates_and_UNKNOWN_does_not_match() {
3366        let expr = "CASE WHEN n > 5 THEN 'big' WHEN n > 0 THEN 'small' ELSE 'none' END";
3367        assert_eq!(v(expr, &json!({"n": 9})), json!("big"));
3368        assert_eq!(v(expr, &json!({"n": 2})), json!("small"));
3369        assert_eq!(v(expr, &json!({"n": -1})), json!("none"));
3370        // An UNKNOWN condition must not match — it falls through to ELSE.
3371        assert_eq!(v(expr, &json!({"other": 1})), json!("none"));
3372    }
3373
3374    #[test]
3375    fn an_ELSE_branch_is_used_when_nothing_matches() {
3376        assert_eq!(
3377            v("CASE k WHEN 'r' THEN 'table' ELSE 'other' END", &json!({"k": "z"})),
3378            json!("other")
3379        );
3380    }
3381
3382    // ── functions ───────────────────────────────────────────────────────────
3383
3384    #[test]
3385    fn the_catalogue_functions_psql_calls_all_answer() {
3386        let r = json!({"o": 10, "enc": 6});
3387        // \dn and \dt both call this for the "Owner" column.
3388        assert_eq!(v("pg_get_userbyid(o)", &r), json!("nedb"));
3389        assert_eq!(v("pg_catalog.pg_get_userbyid(o)", &r), json!("nedb"));
3390        // \dt filters on this. Returning false would hide EVERY table.
3391        assert_eq!(v("pg_table_is_visible(o)", &r), json!(true));
3392        assert_eq!(v("pg_encoding_to_char(enc)", &r), json!("UTF8"));
3393        assert_eq!(v("current_schema", &r), json!("public"));
3394        assert_eq!(v("current_database()", &r), json!("nedb"));
3395        assert_eq!(v("current_user", &r), json!("nedb"));
3396        // The definition-printing functions return NULL rather than invented
3397        // DDL — NEDB has no DDL to print.
3398        assert_eq!(v("pg_get_expr(o, o)", &r), Value::Null);
3399        assert_eq!(v("obj_description(o)", &r), Value::Null);
3400    }
3401
3402    #[test]
3403    fn text_and_null_handling_functions_work() {
3404        let r = json!({"s": "AbC", "n": null});
3405        assert_eq!(v("lower(s)", &r), json!("abc"));
3406        assert_eq!(v("upper(s)", &r), json!("ABC"));
3407        assert_eq!(v("length(s)", &r), json!(3));
3408        assert_eq!(v("lower(n)", &r), Value::Null);
3409        assert_eq!(v("coalesce(n, 'fallback')", &r), json!("fallback"));
3410        assert_eq!(v("coalesce(s, 'fallback')", &r), json!("AbC"));
3411        assert_eq!(v("coalesce(n, n)", &r), Value::Null);
3412        assert_eq!(v("nullif(s, 'AbC')", &r), Value::Null);
3413        assert_eq!(v("nullif(s, 'zz')", &r), json!("AbC"));
3414        // format_type names the type the same way information_schema does.
3415        assert_eq!(v("format_type(20, NULL)", &r), json!("bigint"));
3416    }
3417
3418    #[test]
3419    fn coalesce_does_not_evaluate_past_its_first_non_null() {
3420        // `a / 0` would error; coalesce must never reach it.
3421        let r = json!({"a": 1});
3422        assert_eq!(v("coalesce(a, a / 0)", &r), json!(1));
3423    }
3424
3425    #[test]
3426    fn an_unknown_function_is_REFUSED_rather_than_answered_with_NULL() {
3427        // A NULL column reads as missing DATA rather than a missing feature,
3428        // and somebody would file a data-loss bug against it.
3429        let e = ev("pg_stat_get_numscans(1)", &json!({})).unwrap_err().to_string();
3430        assert!(e.contains("pg_stat_get_numscans"), "{}", e);
3431        assert!(e.contains("refused"), "{}", e);
3432    }
3433
3434    // ── join bindings ───────────────────────────────────────────────────────
3435
3436    #[test]
3437    fn a_qualified_column_reads_only_its_OWN_binding() {
3438        // Both rows have `name`. Without qualifier isolation a join would
3439        // silently read the wrong table's column.
3440        let a = json!({"name": "left", "x": 1});
3441        let b = json!({"name": "right", "y": 2});
3442        let row = Bound {
3443            parts: vec![("a".into(), Some(&a)), ("b".into(), Some(&b))],
3444        };
3445        let get = |e: &str| {
3446            let s = parse(&format!("SELECT {} FROM x", e)).unwrap();
3447            eval(&s.items[0].expr, &row).unwrap()
3448        };
3449        assert_eq!(get("a.name"), json!("left"));
3450        assert_eq!(get("b.name"), json!("right"));
3451        // A bare name takes the first binding that HAS the key.
3452        assert_eq!(get("name"), json!("left"));
3453        assert_eq!(get("y"), json!(2), "a bare name still finds a later binding");
3454    }
3455
3456    #[test]
3457    fn an_unmatched_LEFT_JOIN_side_reads_as_NULL_not_as_a_missing_column() {
3458        // The distinction is what makes `n.nspname IS NULL` answer correctly
3459        // for a row that found no match.
3460        let a = json!({"x": 1});
3461        let row = Bound {
3462            parts: vec![("a".into(), Some(&a)), ("b".into(), None)],
3463        };
3464        let get = |e: &str| {
3465            let s = parse(&format!("SELECT {} FROM x", e)).unwrap();
3466            eval(&s.items[0].expr, &row).unwrap()
3467        };
3468        assert_eq!(get("b.anything"), Value::Null);
3469        assert_eq!(get("b.anything IS NULL"), json!(true));
3470        assert_eq!(get("a.x"), json!(1));
3471    }
3472}
3473
3474#[cfg(test)]
3475mod exec_tests {
3476    use super::*;
3477    use serde_json::json;
3478
3479    /// A resolver over a fixed set of named tables.
3480    fn tables(defs: Vec<(&str, Vec<Value>)>) -> impl Fn(&str) -> Result<Option<Box<dyn Relation>>> {
3481        let owned: Vec<(String, Vec<Value>)> =
3482            defs.into_iter().map(|(n, r)| (n.to_string(), r)).collect();
3483        move |name: &str| {
3484            // Match on the bare name so `pg_catalog.pg_class` finds `pg_class`.
3485            let bare = name.rsplit('.').next().unwrap_or(name);
3486            Ok(owned
3487                .iter()
3488                .find(|(n, _)| n == name || n == bare)
3489                .map(|(_, r)| from_vec(r.clone())))
3490        }
3491    }
3492
3493    fn go(sql: &str, r: &Resolver) -> (Vec<String>, Vec<Value>) {
3494        let (cols, rows) = run(sql, r).unwrap_or_else(|e| panic!("{}\n  -> {}", sql, e));
3495        (cols.into_iter().map(|c| c.name).collect(), rows)
3496    }
3497
3498    fn col(rows: &[Value], name: &str) -> Vec<Value> {
3499        rows.iter().map(|r| r.get(name).cloned().unwrap_or(Value::Null)).collect()
3500    }
3501
3502    // ── the basics, over one table ───────────────────────────────────────────
3503
3504    #[test]
3505    fn select_columns_where_order_limit_offset() {
3506        let t = tables(vec![(
3507            "t",
3508            vec![json!({"a": 3, "s": "c"}), json!({"a": 1, "s": "a"}), json!({"a": 2, "s": "b"})],
3509        )]);
3510        let (names, rows) = go("SELECT a, s FROM t ORDER BY a", &t);
3511        assert_eq!(names, vec!["a", "s"]);
3512        assert_eq!(col(&rows, "a"), vec![json!(1), json!(2), json!(3)]);
3513
3514        let (_, rows) = go("SELECT a FROM t ORDER BY a DESC", &t);
3515        assert_eq!(col(&rows, "a"), vec![json!(3), json!(2), json!(1)]);
3516
3517        let (_, rows) = go("SELECT a FROM t WHERE a > 1 ORDER BY a", &t);
3518        assert_eq!(col(&rows, "a"), vec![json!(2), json!(3)]);
3519
3520        let (_, rows) = go("SELECT a FROM t ORDER BY a LIMIT 2", &t);
3521        assert_eq!(col(&rows, "a"), vec![json!(1), json!(2)]);
3522
3523        let (_, rows) = go("SELECT a FROM t ORDER BY a OFFSET 1", &t);
3524        assert_eq!(col(&rows, "a"), vec![json!(2), json!(3)]);
3525
3526        let (_, rows) = go("SELECT a FROM t ORDER BY a LIMIT 1 OFFSET 1", &t);
3527        assert_eq!(col(&rows, "a"), vec![json!(2)]);
3528
3529        // Past the end is an empty page, not an error.
3530        let (_, rows) = go("SELECT a FROM t OFFSET 99", &t);
3531        assert!(rows.is_empty());
3532    }
3533
3534    #[test]
3535    fn an_output_column_takes_its_alias_or_a_derived_name() {
3536        // Clients index result columns BY NAME, so inventing a different name
3537        // breaks code that works against Postgres.
3538        let t = tables(vec![("t", vec![json!({"a": 1})])]);
3539        assert_eq!(go(r#"SELECT a AS "Name" FROM t"#, &t).0, vec!["Name"]);
3540        assert_eq!(go("SELECT a FROM t", &t).0, vec!["a"]);
3541        assert_eq!(go("SELECT lower('X') FROM t", &t).0, vec!["lower"]);
3542        assert_eq!(go("SELECT 1 + 1 FROM t", &t).0, vec!["?column?"]);
3543        assert_eq!(go("SELECT CASE a WHEN 1 THEN 'x' END FROM t", &t).0, vec!["case"]);
3544    }
3545
3546    #[test]
3547    fn star_expands_from_the_rows_and_a_qualified_star_from_one_binding() {
3548        let t = tables(vec![
3549            ("a", vec![json!({"x": 1, "y": 2})]),
3550            ("b", vec![json!({"z": 3})]),
3551        ]);
3552        let (names, rows) = go("SELECT * FROM a", &t);
3553        assert_eq!(names, vec!["x", "y"]);
3554        assert_eq!(rows.len(), 1);
3555
3556        let (names, _) = go("SELECT a.* FROM a CROSS JOIN b", &t);
3557        assert_eq!(names, vec!["x", "y"], "a qualified star takes ONE binding");
3558
3559        // With no rows a `*` yields no columns, which is the honest answer for
3560        // a schemaless source: only a row knows what columns exist.
3561        let empty = tables(vec![("e", vec![])]);
3562        assert_eq!(go("SELECT * FROM e", &empty).0, Vec::<String>::new());
3563    }
3564
3565    #[test]
3566    fn distinct_dedupes_on_the_projected_values() {
3567        let t = tables(vec![(
3568            "t",
3569            vec![json!({"g": "x"}), json!({"g": "x"}), json!({"g": "y"})],
3570        )]);
3571        let (_, rows) = go("SELECT DISTINCT g FROM t ORDER BY 1", &t);
3572        assert_eq!(col(&rows, "g"), vec![json!("x"), json!("y")]);
3573        let (_, rows) = go("SELECT g FROM t", &t);
3574        assert_eq!(rows.len(), 3, "without DISTINCT every row survives");
3575    }
3576
3577    #[test]
3578    fn order_by_an_ORDINAL_sorts_the_projected_column() {
3579        let t = tables(vec![(
3580            "t",
3581            vec![json!({"a": 2, "b": "z"}), json!({"a": 1, "b": "y"})],
3582        )]);
3583        let (_, rows) = go("SELECT a, b FROM t ORDER BY 1", &t);
3584        assert_eq!(col(&rows, "a"), vec![json!(1), json!(2)]);
3585        let (_, rows) = go("SELECT a, b FROM t ORDER BY 2 DESC", &t);
3586        assert_eq!(col(&rows, "b"), vec![json!("z"), json!("y")]);
3587        // Out of range is an error naming the range, not a silent no-sort.
3588        let e = run("SELECT a FROM t ORDER BY 3", &t).unwrap_err().to_string();
3589        assert!(e.contains("out of range"), "{}", e);
3590    }
3591
3592    #[test]
3593    fn order_by_an_expression_may_use_a_column_NOT_in_the_select_list() {
3594        let t = tables(vec![(
3595            "t",
3596            vec![json!({"a": 1, "hidden": 9}), json!({"a": 2, "hidden": 1})],
3597        )]);
3598        let (_, rows) = go("SELECT a FROM t ORDER BY hidden", &t);
3599        assert_eq!(col(&rows, "a"), vec![json!(2), json!(1)]);
3600    }
3601
3602    #[test]
3603    fn null_ordering_follows_the_direction_defaults() {
3604        let t = tables(vec![(
3605            "t",
3606            vec![json!({"a": 2}), json!({"a": null}), json!({"a": 1})],
3607        )]);
3608        // ASC defaults to NULLS LAST.
3609        assert_eq!(col(&go("SELECT a FROM t ORDER BY a", &t).1, "a"),
3610                   vec![json!(1), json!(2), Value::Null]);
3611        // DESC defaults to NULLS FIRST.
3612        assert_eq!(col(&go("SELECT a FROM t ORDER BY a DESC", &t).1, "a"),
3613                   vec![Value::Null, json!(2), json!(1)]);
3614        // An explicit clause overrides the default.
3615        assert_eq!(col(&go("SELECT a FROM t ORDER BY a NULLS FIRST", &t).1, "a"),
3616                   vec![Value::Null, json!(1), json!(2)]);
3617    }
3618
3619    #[test]
3620    fn a_where_clause_that_is_UNKNOWN_excludes_the_row() {
3621        let t = tables(vec![(
3622            "t",
3623            vec![json!({"a": 1}), json!({"a": null}), json!({"other": 1})],
3624        )]);
3625        // Only the row where the comparison is TRUE survives; UNKNOWN drops.
3626        let (_, rows) = go("SELECT a FROM t WHERE a = 1", &t);
3627        assert_eq!(rows.len(), 1);
3628        // And NOT over UNKNOWN is still UNKNOWN, so it drops too.
3629        let (_, rows) = go("SELECT a FROM t WHERE NOT (a = 1)", &t);
3630        assert_eq!(rows.len(), 0, "NOT UNKNOWN must not resurrect a null row");
3631    }
3632
3633    #[test]
3634    fn select_with_no_FROM_returns_exactly_one_row() {
3635        // A client's liveness probe is written this way.
3636        let t = tables(vec![]);
3637        let (names, rows) = go("SELECT 1", &t);
3638        assert_eq!(rows.len(), 1);
3639        assert_eq!(names, vec!["?column?"]);
3640        assert_eq!(go("SELECT current_schema", &t).1.len(), 1);
3641    }
3642
3643    #[test]
3644    fn an_unknown_relation_is_NAMED_rather_than_answered_with_no_rows() {
3645        // An unknown table that returned zero rows would look exactly like an
3646        // empty one, which is how "where did my data go" starts.
3647        let t = tables(vec![("t", vec![])]);
3648        let e = run("SELECT a FROM nosuchtable", &t).unwrap_err().to_string();
3649        assert!(e.contains("nosuchtable"), "{}", e);
3650        assert!(e.contains("does not exist"), "{}", e);
3651    }
3652
3653    // ── joins ────────────────────────────────────────────────────────────────
3654
3655    #[test]
3656    fn an_inner_join_keeps_only_matching_pairs() {
3657        let t = tables(vec![
3658            ("l", vec![json!({"id": 1, "n": "a"}), json!({"id": 2, "n": "b"})]),
3659            ("r", vec![json!({"lid": 1, "v": "x"})]),
3660        ]);
3661        let (_, rows) = go("SELECT l.n, r.v FROM l JOIN r ON r.lid = l.id", &t);
3662        assert_eq!(rows.len(), 1);
3663        assert_eq!(col(&rows, "n"), vec![json!("a")]);
3664    }
3665
3666    #[test]
3667    fn a_LEFT_join_keeps_unmatched_left_rows_with_NULLs() {
3668        // This is the shape psql's \dt uses twice.
3669        let t = tables(vec![
3670            ("l", vec![json!({"id": 1, "n": "a"}), json!({"id": 2, "n": "b"})]),
3671            ("r", vec![json!({"lid": 1, "v": "x"})]),
3672        ]);
3673        let (_, rows) = go("SELECT l.n, r.v FROM l LEFT JOIN r ON r.lid = l.id ORDER BY 1", &t);
3674        assert_eq!(rows.len(), 2);
3675        assert_eq!(col(&rows, "n"), vec![json!("a"), json!("b")]);
3676        assert_eq!(col(&rows, "v"), vec![json!("x"), Value::Null]);
3677    }
3678
3679    #[test]
3680    fn a_RIGHT_join_keeps_unmatched_right_rows_and_FULL_keeps_both() {
3681        let t = tables(vec![
3682            ("l", vec![json!({"id": 1})]),
3683            ("r", vec![json!({"lid": 1}), json!({"lid": 9})]),
3684        ]);
3685        let (_, rows) = go("SELECT l.id, r.lid FROM l RIGHT JOIN r ON r.lid = l.id", &t);
3686        assert_eq!(rows.len(), 2);
3687        assert!(col(&rows, "id").contains(&Value::Null), "the unmatched right row keeps NULLs on the left");
3688
3689        let t2 = tables(vec![
3690            ("l", vec![json!({"id": 1}), json!({"id": 5})]),
3691            ("r", vec![json!({"lid": 1}), json!({"lid": 9})]),
3692        ]);
3693        let (_, rows) = go("SELECT l.id, r.lid FROM l FULL OUTER JOIN r ON r.lid = l.id", &t2);
3694        assert_eq!(rows.len(), 3, "one match plus one orphan on each side");
3695    }
3696
3697    #[test]
3698    fn a_cross_join_is_the_cartesian_product() {
3699        let t = tables(vec![
3700            ("a", vec![json!({"x": 1}), json!({"x": 2})]),
3701            ("b", vec![json!({"y": 1}), json!({"y": 2}), json!({"y": 3})]),
3702        ]);
3703        assert_eq!(go("SELECT a.x, b.y FROM a CROSS JOIN b", &t).1.len(), 6);
3704        // A comma FROM list means the same thing.
3705        assert_eq!(go("SELECT a.x, b.y FROM a, b", &t).1.len(), 6);
3706    }
3707
3708    #[test]
3709    fn an_ON_clause_that_is_UNKNOWN_does_not_join() {
3710        // Treating UNKNOWN as a match would invent pairings out of missing
3711        // data — rows that exist in neither table.
3712        let t = tables(vec![
3713            ("l", vec![json!({"id": null})]),
3714            ("r", vec![json!({"lid": null})]),
3715        ]);
3716        let (_, rows) = go("SELECT l.id FROM l JOIN r ON r.lid = l.id", &t);
3717        assert!(rows.is_empty(), "NULL = NULL is UNKNOWN, so nothing joins");
3718        // …and on a LEFT JOIN the left row survives with NULLs.
3719        let (_, rows) = go("SELECT l.id FROM l LEFT JOIN r ON r.lid = l.id", &t);
3720        assert_eq!(rows.len(), 1);
3721    }
3722
3723    #[test]
3724    fn two_joins_chain() {
3725        let t = tables(vec![
3726            ("a", vec![json!({"id": 1, "bid": 10, "cid": 100})]),
3727            ("b", vec![json!({"id": 10, "bn": "B"})]),
3728            ("c", vec![json!({"id": 100, "cn": "C"})]),
3729        ]);
3730        let (_, rows) = go(
3731            "SELECT a.id, b.bn, c.cn FROM a \
3732             LEFT JOIN b ON b.id = a.bid \
3733             LEFT JOIN c ON c.id = a.cid",
3734            &t,
3735        );
3736        assert_eq!(rows.len(), 1);
3737        assert_eq!(col(&rows, "bn"), vec![json!("B")]);
3738        assert_eq!(col(&rows, "cn"), vec![json!("C")]);
3739    }
3740
3741    // ── THE acceptance tests ─────────────────────────────────────────────────
3742
3743    /// The catalogue rows psql's `\dn` and `\dt` actually read.
3744    fn catalog() -> impl Fn(&str) -> Result<Option<Box<dyn Relation>>> {
3745        tables(vec![
3746            (
3747                "pg_namespace",
3748                vec![
3749                    json!({"oid": 2200, "nspname": "public", "nspowner": 10}),
3750                    json!({"oid": 11, "nspname": "pg_catalog", "nspowner": 10}),
3751                    json!({"oid": 13000, "nspname": "information_schema", "nspowner": 10}),
3752                ],
3753            ),
3754            (
3755                "pg_class",
3756                vec![
3757                    json!({"oid": 16401, "relname": "orders", "relnamespace": 2200,
3758                           "relkind": "r", "relowner": 10, "relam": 2}),
3759                    json!({"oid": 16402, "relname": "drivers", "relnamespace": 2200,
3760                           "relkind": "r", "relowner": 10, "relam": 2}),
3761                ],
3762            ),
3763            ("pg_am", vec![json!({"oid": 2, "amname": "heap"})]),
3764        ])
3765    }
3766
3767    #[test]
3768    fn THE_dn_QUERY_RUNS_AND_RETURNS_THE_RIGHT_ROWS() {
3769        let (names, rows) = go(
3770            r#"SELECT n.nspname AS "Name",
3771                 pg_catalog.pg_get_userbyid(n.nspowner) AS "Owner"
3772               FROM pg_catalog.pg_namespace n
3773               WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'
3774               ORDER BY 1;"#,
3775            &catalog(),
3776        );
3777
3778        assert_eq!(names, vec!["Name", "Owner"], "psql reads these BY NAME");
3779        // `pg_catalog` is excluded by the regex, `information_schema` by the
3780        // `<>` — leaving exactly the one schema a user cares about.
3781        assert_eq!(col(&rows, "Name"), vec![json!("public")]);
3782        assert_eq!(col(&rows, "Owner"), vec![json!("nedb")]);
3783    }
3784
3785    #[test]
3786    fn THE_dt_QUERY_RUNS_AND_RETURNS_THE_RIGHT_ROWS() {
3787        let (names, rows) = go(
3788            r#"SELECT n.nspname as "Schema",
3789                 c.relname as "Name",
3790                 CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view'
3791                   WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index'
3792                   WHEN 'S' THEN 'sequence' WHEN 't' THEN 'TOAST table'
3793                   WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'partitioned table'
3794                   WHEN 'I' THEN 'partitioned index' END as "Type",
3795                 pg_catalog.pg_get_userbyid(c.relowner) as "Owner"
3796               FROM pg_catalog.pg_class c
3797                    LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
3798                    LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam
3799               WHERE c.relkind IN ('r','p','')
3800                     AND n.nspname <> 'pg_catalog'
3801                     AND n.nspname !~ '^pg_toast'
3802                     AND n.nspname <> 'information_schema'
3803                 AND pg_catalog.pg_table_is_visible(c.oid)
3804               ORDER BY 1,2;"#,
3805            &catalog(),
3806        );
3807
3808        assert_eq!(names, vec!["Schema", "Name", "Type", "Owner"]);
3809        // ORDER BY 1,2 — schema then name, so `drivers` precedes `orders`.
3810        assert_eq!(col(&rows, "Name"), vec![json!("drivers"), json!("orders")]);
3811        assert_eq!(col(&rows, "Schema"), vec![json!("public"), json!("public")]);
3812        // The nine-branch CASE resolves relkind 'r'.
3813        assert_eq!(col(&rows, "Type"), vec![json!("table"), json!("table")]);
3814        assert_eq!(col(&rows, "Owner"), vec![json!("nedb"), json!("nedb")]);
3815    }
3816
3817    #[test]
3818    fn the_dt_query_still_filters_correctly_with_a_system_relation_present() {
3819        // A relation in pg_catalog must be excluded by the `<>`, and one with
3820        // an unlisted relkind by the IN list. If either filter were dropped —
3821        // the bug the parser restructure fixed — \dt would list internals.
3822        let t = tables(vec![
3823            (
3824                "pg_namespace",
3825                vec![
3826                    json!({"oid": 2200, "nspname": "public", "nspowner": 10}),
3827                    json!({"oid": 11, "nspname": "pg_catalog", "nspowner": 10}),
3828                ],
3829            ),
3830            (
3831                "pg_class",
3832                vec![
3833                    json!({"oid": 1, "relname": "mine", "relnamespace": 2200,
3834                           "relkind": "r", "relowner": 10, "relam": 2}),
3835                    json!({"oid": 2, "relname": "pg_internal", "relnamespace": 11,
3836                           "relkind": "r", "relowner": 10, "relam": 2}),
3837                    json!({"oid": 3, "relname": "an_index", "relnamespace": 2200,
3838                           "relkind": "i", "relowner": 10, "relam": 2}),
3839                ],
3840            ),
3841            ("pg_am", vec![json!({"oid": 2, "amname": "heap"})]),
3842        ]);
3843        let (_, rows) = go(
3844            r#"SELECT c.relname as "Name" FROM pg_catalog.pg_class c
3845                 LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
3846               WHERE c.relkind IN ('r','p','') AND n.nspname <> 'pg_catalog'
3847               ORDER BY 1"#,
3848            &t,
3849        );
3850        assert_eq!(col(&rows, "Name"), vec![json!("mine")],
3851                   "a system relation and an index must both be filtered out");
3852    }
3853}
3854
3855#[cfg(test)]
3856mod operator_syntax_tests {
3857    use super::*;
3858    use serde_json::json;
3859
3860    #[test]
3861    fn the_OPERATOR_qualification_psql_generates_is_understood() {
3862        // `\d` writes every operator this way:
3863        //   c.relname OPERATOR(pg_catalog.~) '^(orders)$'
3864        // It names exactly the operator it wraps, so the schema is dropped.
3865        let s = parse(
3866            "SELECT a FROM t WHERE n OPERATOR(pg_catalog.~) '^x' \
3867             AND m OPERATOR(pg_catalog.=) 1",
3868        )
3869        .expect("psql's OPERATOR() form must parse");
3870        match s.where_.unwrap() {
3871            Expr::Binary { op, left, .. } => {
3872                assert_eq!(op, "AND");
3873                assert!(matches!(*left, Expr::Binary { ref op, .. } if op == "~"));
3874            }
3875            other => panic!("{:?}", other),
3876        }
3877    }
3878
3879    #[test]
3880    fn an_OPERATOR_qualified_comparison_EVALUATES() {
3881        let t = |_: &str| -> Result<Option<Box<dyn Relation>>> {
3882            Ok(Some(from_vec(vec![json!({"n": "orders"}), json!({"n": "pg_toast_1"})])))
3883        };
3884        let (_, rows) = run(
3885            "SELECT n FROM pg_class WHERE n OPERATOR(pg_catalog.~) '^ord'",
3886            &t,
3887        )
3888        .unwrap();
3889        assert_eq!(rows.len(), 1);
3890        assert_eq!(rows[0]["n"], json!("orders"));
3891    }
3892
3893    #[test]
3894    fn a_subquery_an_ARRAY_constructor_and_EXISTS_are_all_refused_BY_NAME() {
3895        // "expected ')', got SELECT" is a parser internal and tells the reader
3896        // nothing about what to change. These are the constructs `\d` and
3897        // `\dp` actually hinge on, so these are the messages someone reads.
3898        for (sql, needle) in [
3899            ("SELECT a FROM t WHERE x = (SELECT 1)", "subquery"),
3900            ("SELECT array_to_string(ARRAY(SELECT a FROM b), ',') FROM t", "ARRAY"),
3901            ("SELECT a FROM t WHERE EXISTS (SELECT 1)", "EXISTS"),
3902        ] {
3903            let e = parse(sql).unwrap_err().to_string();
3904            assert!(e.contains(needle), "{} -> {}", sql, e);
3905        }
3906    }
3907}
3908
3909#[cfg(test)]
3910mod collate_tests {
3911    use super::*;
3912    use serde_json::json;
3913
3914    #[test]
3915    fn COLLATE_is_consumed_because_it_cannot_change_the_answer() {
3916        // psql writes `COLLATE pg_catalog."C"` throughout `\d`. NEDB has one
3917        // collation, so refusing a clause that provably has no effect would
3918        // reject a query whose result is already correct.
3919        for sql in [
3920            r#"SELECT a FROM t ORDER BY a COLLATE "C""#,
3921            r#"SELECT a COLLATE "C" FROM t"#,
3922            r#"SELECT a FROM t WHERE a COLLATE pg_catalog."C" = 'x'"#,
3923        ] {
3924            parse(sql).unwrap_or_else(|e| panic!("{} -> {}", sql, e));
3925        }
3926        // A malformed COLLATE is still an error rather than silently skipped.
3927        assert!(parse("SELECT a FROM t ORDER BY a COLLATE").is_err());
3928    }
3929
3930    #[test]
3931    fn a_COLLATE_annotated_comparison_still_evaluates() {
3932        let t = |_: &str| -> Result<Option<Box<dyn Relation>>> {
3933            Ok(Some(from_vec(vec![json!({"n": "b"}), json!({"n": "a"})])))
3934        };
3935        let (_, rows) = run(r#"SELECT n FROM pg_class ORDER BY n COLLATE "C""#, &t).unwrap();
3936        assert_eq!(rows[0]["n"], json!("a"), "the ORDER BY still sorts");
3937    }
3938}