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