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