Skip to main content

nedb_engine/
sqlselect.rs

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