Skip to main content

spg_engine/
subquery.rs

1//! Correlated-subquery evaluation split out of `lib.rs` (lib.rs split
2//! 5): the per-row `eval_expr_with_correlated` path (clones the
3//! expression, substitutes outer-row columns into each surviving
4//! subquery node, runs the inner SELECT, folds the literal result back)
5//! plus the `subquery_replacement` pre-walk that materialises
6//! uncorrelated subquery nodes once, and the `try_batch_correlated_scalar`
7//! keyed-probe optimisation (round-22 phase 3) that runs a correlated
8//! scalar subquery ONCE without the correlation and folds rows into a
9//! key→value map. `impl Engine` methods; the bare-SELECT / DML / join
10//! row loops drive `eval_expr_with_correlated`, and `select.rs` drives
11//! `subquery_replacement` / `try_batch_correlated_scalar`.
12
13use alloc::string::String;
14use alloc::vec::Vec;
15
16use spg_sql::ast::{
17    BinOp, ColumnName, Cte, Expr, FromJoin, JoinKind, LimitExpr, Literal, SelectItem,
18    SelectStatement, TableRef, UnOp,
19};
20
21/// v7.37.4 — fire counter for the LIMIT 1 pullup pass. Tests inspect
22/// this to confirm whether the rewrite actually triggered on a given
23/// SQL shape (semantic-equivalence tests pass either way). Relaxed
24/// ordering is fine: tests synchronize on full query execution.
25pub static PULLUP_LIMIT1_FIRE_COUNT: core::sync::atomic::AtomicU64 =
26    core::sync::atomic::AtomicU64::new(0);
27
28/// v7.37.4 A' — per-keyed-probe / per-fallback counters for the
29/// batched scalar subquery resolver. Used by perf-gate instrumentation
30/// to distinguish "keyed path fires but per-probe is slow" from
31/// "keyed path never fires" — A' targets the former.
32pub static BATCHED_SCALAR_KEYED_FIRE_COUNT: core::sync::atomic::AtomicU64 =
33    core::sync::atomic::AtomicU64::new(0);
34pub static BATCHED_SCALAR_KEYED_PROBE_COUNT: core::sync::atomic::AtomicU64 =
35    core::sync::atomic::AtomicU64::new(0);
36pub static BATCHED_SCALAR_FALL_THROUGH_COUNT: core::sync::atomic::AtomicU64 =
37    core::sync::atomic::AtomicU64::new(0);
38
39/// v7.37.4 A' — EXISTS path counters. Distinguish whether mailrs
40/// prod's 2-column NOT EXISTS goes through the cheap
41/// `try_batch_correlated_exists` (one inner scan + per-row hash
42/// probe) or the slow `pull_up_exists_sublinks` rewrite (rejects
43/// multi-column correlation today). Ablation finding 2026-06-19:
44/// the NOT EXISTS conjunct in `/api/conversations` costs ~165 ms
45/// per 100k bench iteration — figure out which path is actually
46/// being taken.
47pub static EXISTS_PULLUP_FIRE_COUNT: core::sync::atomic::AtomicU64 =
48    core::sync::atomic::AtomicU64::new(0);
49pub static EXISTS_BATCH_FIRE_COUNT: core::sync::atomic::AtomicU64 =
50    core::sync::atomic::AtomicU64::new(0);
51pub static EXISTS_BATCH_FALL_THROUGH_COUNT: core::sync::atomic::AtomicU64 =
52    core::sync::atomic::AtomicU64::new(0);
53
54/// v7.37.4 A'' — differential knob. When true, the multi-column
55/// branch of `try_pull_up_exists_sublink` rejects (falling back to
56/// the v7.34.2 batch resolver path); single-column EXISTS pullup
57/// still fires. Lets the differential e2e prove byte-equal results
58/// between the new pullup path and the legacy batch path. Default
59/// false — production never sets this.
60pub static EXISTS_PULLUP_MULTICOL_DISABLE: core::sync::atomic::AtomicBool =
61    core::sync::atomic::AtomicBool::new(false);
62
63use spg_storage::{Row, Value};
64
65use crate::eval::{self, EvalContext};
66use crate::substitute::value_to_literal_expr;
67use crate::{
68    CancelToken, Engine, EngineError, QueryResult, aggregate, memoize, order_by_value_cmp, reorder,
69    value_cmp, visit_expr_columns_and_subqueries,
70};
71
72impl Engine {
73    /// v4.23: per-row eval that handles correlated subqueries.
74    /// Equivalent to `eval::eval_expr` when the expression has no
75    /// subqueries; otherwise clones the expression, substitutes
76    /// outer-row columns into each surviving subquery node, runs
77    /// the inner SELECT, and replaces the node with the literal
78    /// result. Only the WHERE-filter call sites use this path so
79    /// the uncorrelated fast path is preserved everywhere else.
80    pub(crate) fn eval_expr_with_correlated(
81        &self,
82        expr: &Expr,
83        row: &Row<'static>,
84        ctx: &EvalContext<'_>,
85        cancel: CancelToken<'_>,
86        mut memo: Option<&mut memoize::MemoizeCache>,
87    ) -> Result<Value<'static>, EngineError> {
88        // v7.30.2 (mailrs round-25) — the has-subquery walk is
89        // O(tree) and a materialised `IN (…)` list makes the tree
90        // huge; cache the answer per expression address so the
91        // per-row dispatch stops re-walking 24k list elements.
92        let has_subq = if let Some(m) = memo.as_deref_mut() {
93            let key = core::ptr::from_ref::<Expr>(expr) as usize;
94            match m.has_subquery.get(&key) {
95                Some(b) => *b,
96                None => {
97                    let b = expr_has_subquery(expr);
98                    m.has_subquery.insert(key, b);
99                    b
100                }
101            }
102        } else {
103            expr_has_subquery(expr)
104        };
105        if !has_subq {
106            // A large materialised `IN (…)` list inside the WHERE
107            // makes the plain eval O(rows × list); route through the
108            // per-query membership set (built once, keyed by node
109            // address) when one is reachable on the AND spine.
110            if let Some(m) = memo.as_deref_mut()
111                && expr_may_use_in_set(expr)
112            {
113                return eval_with_in_sets(expr, row, ctx, m);
114            }
115            return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
116        }
117        // v7.29 (3c) - per-expression plan: the batch maps for this
118        // host expression's scalar subqueries are looked up by the
119        // expression's ADDRESS (stable across the row loop), so the
120        // hot path does zero AST formatting. Building the plan (and
121        // its Display-keyed group maps) happens once per expression.
122        if let Some(m) = memo.as_deref_mut() {
123            let key = core::ptr::from_ref::<Expr>(expr) as usize;
124            // Plan hit: skip the collection walk entirely (it ran
125            // once per group otherwise - 70k walks per inbox query).
126            // The memo is per-query and host expressions outlive it,
127            // so an address that hit once stays valid.
128            let plan_hit = m.expr_plans.contains_key(&key);
129            let exists_plan_hit = m.exists_plans.contains_key(&key);
130            let mut subs: Vec<&SelectStatement> = Vec::new();
131            let mut exists_subs: Vec<&SelectStatement> = Vec::new();
132            if !plan_hit {
133                collect_scalar_subqueries(expr, &mut subs);
134            }
135            if !exists_plan_hit {
136                collect_exists_subqueries(expr, &mut exists_subs);
137            }
138            if !plan_hit && !subs.is_empty() {
139                let mut plan: Vec<Option<alloc::rc::Rc<memoize::GroupMap>>> =
140                    Vec::with_capacity(subs.len());
141                for sub in &subs {
142                    let repr = alloc::format!("{sub}");
143                    if !m.group_maps.contains_key(&repr) {
144                        let built = self
145                            .try_batch_correlated_scalar(sub, None, cancel)?
146                            .map(alloc::rc::Rc::new);
147                        m.group_maps.insert(repr.clone(), built);
148                    }
149                    plan.push(m.group_maps.get(&repr).cloned().flatten());
150                }
151                let mut template = expr.clone();
152                hollow_scalar_subqueries(&mut template);
153                m.expr_plans.insert(key, (subs.len(), plan, template));
154            }
155            // v7.34.2 — parallel EXISTS plan. Walk host ONCE in pre-order,
156            // build a decorrelated key-set for each EXISTS subquery via
157            // `try_batch_correlated_exists`, and cache the vec by host_ptr.
158            // Per-row dispatch below uses `splice_planned_exists` which
159            // increments an ordinal cursor — no `alloc::format!` per row.
160            if !exists_plan_hit && !exists_subs.is_empty() {
161                let mut eplan: Vec<Option<alloc::rc::Rc<memoize::ExistsSet>>> =
162                    Vec::with_capacity(exists_subs.len());
163                for sub in &exists_subs {
164                    let built = self
165                        .try_batch_correlated_exists(sub, cancel)?
166                        .map(alloc::rc::Rc::new);
167                    if built.is_some() {
168                        EXISTS_BATCH_FIRE_COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
169                    } else {
170                        EXISTS_BATCH_FALL_THROUGH_COUNT
171                            .fetch_add(1, core::sync::atomic::Ordering::Relaxed);
172                    }
173                    eplan.push(built);
174                }
175                m.exists_plans.insert(key, eplan);
176            }
177            // Fast-path gate: take it if we have a planned scalar set, a
178            // planned EXISTS set, or both — anything that lets us skip
179            // the per-row `expr.clone()` + `resolve_correlated_in_expr`
180            // dispatch for the corresponding subquery class.
181            let scalar_ready = m
182                .expr_plans
183                .get(&key)
184                .map(|(_, plan, _)| !plan.is_empty() && plan.iter().all(|p| p.is_some()))
185                .unwrap_or(false);
186            let exists_ready = m
187                .exists_plans
188                .get(&key)
189                .map(|plan| !plan.is_empty() && plan.iter().all(|p| p.is_some()))
190                .unwrap_or(false);
191            if scalar_ready || exists_ready {
192                // Fast path: every planned subquery resolves via its
193                // map; clone the (hollowed-where-scalar) template,
194                // splice map values, eval. EXISTS bodies are NOT
195                // hollowed (we don't traverse into them during splice —
196                // `splice_planned_exists` consumes the EXISTS node
197                // wholesale), so cloning the original `expr` works for
198                // the EXISTS-only path.
199                let scalar_plan = m
200                    .expr_plans
201                    .get(&key)
202                    .map(|(_, plan, template)| (plan.clone(), template.clone()));
203                let exists_plan = m.exists_plans.get(&key).cloned();
204                let mut e = match &scalar_plan {
205                    Some((_, template)) => template.clone(),
206                    None => expr.clone(),
207                };
208                let mut all_ok = true;
209                if let Some((plan, _)) = &scalar_plan {
210                    let mut idx = 0usize;
211                    all_ok &= splice_planned_subqueries(&mut e, plan, &mut idx, row, ctx)?;
212                }
213                if all_ok && let Some(plan) = &exists_plan {
214                    let mut idx = 0usize;
215                    all_ok &= splice_planned_exists(&mut e, plan, &mut idx, row, ctx)?;
216                }
217                if all_ok {
218                    if expr_has_subquery(&e) {
219                        self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
220                    }
221                    return eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval);
222                }
223            }
224        }
225        let mut e = expr.clone();
226        self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
227        eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
228    }
229
230    fn resolve_correlated_in_expr(
231        &self,
232        e: &mut Expr,
233        row: &Row<'static>,
234        ctx: &EvalContext<'_>,
235        cancel: CancelToken<'_>,
236        mut memo: Option<&mut memoize::MemoizeCache>,
237    ) -> Result<(), EngineError> {
238        match e {
239            Expr::AggregateOrdered { call, order_by, .. } => {
240                self.resolve_correlated_in_expr(call, row, ctx, cancel, memo.as_deref_mut())?;
241                for o in order_by.iter_mut() {
242                    self.resolve_correlated_in_expr(
243                        &mut o.expr,
244                        row,
245                        ctx,
246                        cancel,
247                        memo.as_deref_mut(),
248                    )?;
249                }
250            }
251            Expr::ScalarSubquery(inner) => {
252                // v7.29 (round-22 phase 3) — batch path first: a
253                // correlated scalar of the `inner_col = outer_col
254                // [ORDER BY … LIMIT 1]` shape evaluates ONCE as a
255                // grouped scan; per-row resolution becomes a map
256                // lookup. 23.5k per-group executions (~900 ms) became
257                // one scan + lookups.
258                // v7.37.x (docker-fair SCALARSQ attack) — pointer-keyed
259                // fast cache. The inner SelectStatement is stable for
260                // the duration of the query, so its address makes a
261                // unique key that costs nothing to compute (vs
262                // `alloc::format!("{}", inner)` ~ 500 ns × N outer
263                // rows of pure repr churn).
264                if memo.is_some() {
265                    let ptr_key = core::ptr::from_ref::<SelectStatement>(&**inner) as usize;
266                    let entry_known = memo
267                        .as_ref()
268                        .is_some_and(|m| m.group_maps_by_ptr.contains_key(&ptr_key));
269                    if !entry_known {
270                        let built = self
271                            .try_batch_correlated_scalar(inner, None, cancel)?
272                            .map(alloc::rc::Rc::new);
273                        if let Some(m) = memo.as_deref_mut() {
274                            m.group_maps_by_ptr.insert(ptr_key, built);
275                        }
276                    }
277                    if let Some(m) = memo.as_deref_mut()
278                        && let Some(Some(gm)) = m.group_maps_by_ptr.get(&ptr_key)
279                    {
280                        let (outer_col, map, empty_default) = gm.as_ref();
281                        let key_v = eval::eval_expr(&Expr::Column(outer_col.clone()), row, ctx)
282                            .map_err(EngineError::Eval)?;
283                        // v7.37.x — scalar subquery empty-set semantics:
284                        // `COUNT(*)` / `COUNT(col)` over no rows = 0,
285                        // every other aggregate = NULL. The batched
286                        // GroupMap omits keys whose inner-table partition
287                        // was empty; treat such misses as the per-
288                        // aggregate empty-default.
289                        let v = if matches!(key_v, Value::Null) {
290                            Value::Null
291                        } else {
292                            map.get(&aggregate::encode_key(core::slice::from_ref(&key_v)))
293                                .cloned()
294                                .unwrap_or_else(|| empty_default.clone())
295                        };
296                        *e = value_to_literal_expr(v)?;
297                        return Ok(());
298                    }
299                }
300                // v6.2.6 — Memoize: build the cache key from the
301                // pre-substitution subquery repr + the outer row's
302                // values. Two outer rows with identical correlated
303                // values hit the same entry.
304                let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
305                    subquery_repr: alloc::format!("{}", **inner),
306                    outer_values: row.values.iter().cloned().map(Value::into_owned).collect(),
307                });
308                if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
309                    && let Some(cached) = cache.get(k)
310                {
311                    *e = value_to_literal_expr(cached)?;
312                    return Ok(());
313                }
314                // v7.37.x (docker-fair SCALARSQ attack) — direct PK probe
315                // fast path. The shape
316                //   (SELECT COUNT(*) FROM T WHERE T.pk = outer.col)
317                // — common SCALARSQ shape and what the docker-fair
318                // SCALARSQ benchmark exercises — is a 1-bit lookup:
319                // the probe either finds 1 row or 0. Skip
320                // `exec_select_cancel`'s parse / resolve / plan /
321                // aggregate roundtrip; do an index seek on T.pk
322                // directly and return `Int(0)` or `Int(1)`. PG with a
323                // cached prepared plan does roughly this; SCALARSQ
324                // drops from per-row ~3 µs to per-row ~100 ns.
325                if let Some(v) = self.try_scalar_count_pk_eq_probe(inner, row, ctx)? {
326                    *e = value_to_literal_expr(v)?;
327                    return Ok(());
328                }
329                let mut s = (**inner).clone();
330                substitute_outer_columns(&mut s, row, ctx);
331                let r = self.exec_select_cancel(&s, cancel)?;
332                let QueryResult::Rows { rows, .. } = r else {
333                    return Err(EngineError::Unsupported(
334                        "scalar subquery: inner did not return rows".into(),
335                    ));
336                };
337                let value = match rows.as_slice() {
338                    [] => Value::Null,
339                    [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
340                    _ => {
341                        return Err(EngineError::Unsupported(alloc::format!(
342                            "scalar subquery returned {} rows; expected 0 or 1",
343                            rows.len()
344                        )));
345                    }
346                };
347                if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
348                    cache.insert(k, value.clone());
349                }
350                *e = value_to_literal_expr(value)?;
351            }
352            Expr::Exists { subquery, negated } => {
353                // v7.34 (mailrs conn-pool P0) — semi/anti-join batch path
354                // first: a correlated `[NOT] EXISTS` of the
355                // `inner.k = outer.col [AND inner-preds]` shape builds its
356                // inner key-set ONCE (keyed by repr in the per-query memo);
357                // per-row resolution becomes a membership test. 24k per-row
358                // inner executions became one scan + 24k lookups.
359                if memo.is_some() {
360                    let repr = alloc::format!("{}", **subquery);
361                    let known = memo
362                        .as_ref()
363                        .is_some_and(|m| m.exists_sets.contains_key(&repr));
364                    if !known {
365                        let built = self
366                            .try_batch_correlated_exists(subquery, cancel)?
367                            .map(alloc::rc::Rc::new);
368                        if let Some(m) = memo.as_deref_mut() {
369                            m.exists_sets.insert(repr.clone(), built);
370                        }
371                    }
372                    if let Some(m) = memo.as_deref_mut()
373                        && let Some(Some(es)) = m.exists_sets.get(&repr)
374                    {
375                        let (outer_cols, set) = es.as_ref();
376                        let mut key_vals: Vec<Value<'static>> =
377                            Vec::with_capacity(outer_cols.len());
378                        let mut any_null = false;
379                        for oc in outer_cols {
380                            let v = eval::eval_expr(&Expr::Column(oc.clone()), row, ctx)
381                                .map_err(EngineError::Eval)?;
382                            if matches!(v, Value::Null) {
383                                any_null = true;
384                            }
385                            key_vals.push(v);
386                        }
387                        // NULL key component → never matches → not present.
388                        let present = !any_null && set.contains(&aggregate::encode_key(&key_vals));
389                        let bit = if *negated { !present } else { present };
390                        *e = Expr::Literal(Literal::Bool(bit));
391                        return Ok(());
392                    }
393                }
394                let mut s = (**subquery).clone();
395                substitute_outer_columns(&mut s, row, ctx);
396                let r = self.exec_select_cancel(&s, cancel)?;
397                let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
398                let bit = if *negated { !exists } else { exists };
399                *e = Expr::Literal(Literal::Bool(bit));
400            }
401            Expr::InSubquery {
402                expr: lhs,
403                subquery,
404                negated,
405            } => {
406                self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
407                let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
408                let mut s = (**subquery).clone();
409                substitute_outer_columns(&mut s, row, ctx);
410                let r = self.exec_select_cancel(&s, cancel)?;
411                let QueryResult::Rows { columns, rows, .. } = r else {
412                    return Err(EngineError::Unsupported(
413                        "IN-subquery: inner did not return rows".into(),
414                    ));
415                };
416                if columns.len() != 1 {
417                    return Err(EngineError::Unsupported(alloc::format!(
418                        "IN-subquery must project exactly one column; got {}",
419                        columns.len()
420                    )));
421                }
422                let mut found = false;
423                let mut any_null = false;
424                for r0 in rows {
425                    let v = r0.values.into_iter().next().unwrap_or(Value::Null);
426                    if v.is_null() {
427                        any_null = true;
428                        continue;
429                    }
430                    if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
431                        found = true;
432                        break;
433                    }
434                }
435                let bit = if found {
436                    !*negated
437                } else if any_null {
438                    return Err(EngineError::Unsupported(
439                        "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
440                    ));
441                } else {
442                    *negated
443                };
444                *e = Expr::Literal(Literal::Bool(bit));
445            }
446            Expr::Binary { lhs, rhs, .. } => {
447                self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
448                self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
449            }
450            Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
451                self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
452            }
453            Expr::Like { expr, pattern, .. } => {
454                self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
455                self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
456            }
457            Expr::FunctionCall { args, .. } => {
458                for a in args {
459                    self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
460                }
461            }
462            Expr::Extract { source, .. } => {
463                self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
464            }
465            Expr::WindowFunction { .. }
466            | Expr::Literal(_)
467            | Expr::Placeholder(_)
468            | Expr::Column(_) => {}
469            // v7.10.10 — recurse children.
470            Expr::Array(items) => {
471                for elem in items {
472                    self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
473                }
474            }
475            Expr::ArraySubscript { target, index } => {
476                self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
477                self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
478            }
479            Expr::AnyAll { expr, array, .. } => {
480                self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
481                self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
482            }
483            Expr::InList { expr, list, .. } => {
484                self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
485                for item in list {
486                    self.resolve_correlated_in_expr(item, row, ctx, cancel, memo.as_deref_mut())?;
487                }
488            }
489            Expr::Case {
490                operand,
491                branches,
492                else_branch,
493            } => {
494                if let Some(o) = operand {
495                    self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
496                }
497                for (w, t) in branches {
498                    self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
499                    self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
500                }
501                if let Some(e) = else_branch {
502                    self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
503                }
504            }
505        }
506        Ok(())
507    }
508
509    /// v4.10: pre-walk the WHERE / projection / etc. of a SELECT and
510    /// replace every subquery node with a materialised literal. SPG
511    /// only supports uncorrelated subqueries — the inner SELECT does
512    /// not see outer-row columns, so the result is the same for every
513    /// outer row and can be evaluated once.
514    ///
515    /// Returns the rewritten statement; the caller passes this to the
516    /// regular row-loop executor which no longer sees Subquery nodes
517    /// in its tree.
518    pub(crate) fn subquery_replacement(
519        &self,
520        e: &Expr,
521        cancel: CancelToken<'_>,
522    ) -> Result<Option<Expr>, EngineError> {
523        match e {
524            Expr::ScalarSubquery(inner) => {
525                // v7.32 (R30) — a correlated subquery is resolved by
526                // the per-row / post-LIMIT correlated path; executing
527                // it here only to catch the correlation error first
528                // materialises (and discards) its whole inner FROM.
529                if select_is_correlated(inner) {
530                    return Ok(None);
531                }
532                let mut s = (**inner).clone();
533                // Recurse into the inner SELECT first so nested
534                // subqueries materialise bottom-up.
535                self.resolve_select_subqueries(&mut s, cancel)?;
536                let r = match self.exec_bare_select_cancel(&s, cancel) {
537                    Ok(r) => r,
538                    Err(e) if is_correlation_error(&e) => return Ok(None),
539                    Err(e) => return Err(e),
540                };
541                let QueryResult::Rows { rows, .. } = r else {
542                    return Err(EngineError::Unsupported(
543                        "scalar subquery: inner statement did not return rows".into(),
544                    ));
545                };
546                let value = match rows.as_slice() {
547                    [] => Value::Null,
548                    [row] => row.values.first().cloned().unwrap_or(Value::Null),
549                    _ => {
550                        return Err(EngineError::Unsupported(alloc::format!(
551                            "scalar subquery returned {} rows; expected 0 or 1",
552                            rows.len()
553                        )));
554                    }
555                };
556                Ok(Some(value_to_literal_expr(value)?))
557            }
558            Expr::Exists { subquery, negated } => {
559                if select_is_correlated(subquery) {
560                    return Ok(None);
561                }
562                let mut s = (**subquery).clone();
563                self.resolve_select_subqueries(&mut s, cancel)?;
564                let r = match self.exec_bare_select_cancel(&s, cancel) {
565                    Ok(r) => r,
566                    Err(e) if is_correlation_error(&e) => return Ok(None),
567                    Err(e) => return Err(e),
568                };
569                let exists = match r {
570                    QueryResult::Rows { rows, .. } => !rows.is_empty(),
571                    QueryResult::CommandOk { .. } => false,
572                };
573                let bit = if *negated { !exists } else { exists };
574                Ok(Some(Expr::Literal(Literal::Bool(bit))))
575            }
576            Expr::InSubquery {
577                expr,
578                subquery,
579                negated,
580            } => {
581                if select_is_correlated(subquery) {
582                    return Ok(None);
583                }
584                let mut s = (**subquery).clone();
585                self.resolve_select_subqueries(&mut s, cancel)?;
586                let r = match self.exec_bare_select_cancel(&s, cancel) {
587                    Ok(r) => r,
588                    Err(e) if is_correlation_error(&e) => return Ok(None),
589                    Err(e) => return Err(e),
590                };
591                let QueryResult::Rows { columns, rows, .. } = r else {
592                    return Err(EngineError::Unsupported(
593                        "IN-subquery: inner statement did not return rows".into(),
594                    ));
595                };
596                if columns.len() != 1 {
597                    return Err(EngineError::Unsupported(alloc::format!(
598                        "IN-subquery must project exactly one column; got {}",
599                        columns.len()
600                    )));
601                }
602                // v7.30.2 (mailrs round-25) — flat InList, NOT an OR-Eq
603                // chain: chain depth scaled with the inner result's ROW
604                // COUNT, so one 24k-match search overflowed the worker
605                // stack (recursive eval + recursive Box drop) and
606                // aborted the embedding host process.
607                let mut list: Vec<Expr> = Vec::with_capacity(rows.len());
608                for row in rows {
609                    let v = row.values.into_iter().next().unwrap_or(Value::Null);
610                    list.push(value_to_literal_expr(v)?);
611                }
612                Ok(Some(Expr::InList {
613                    expr: expr.clone(),
614                    list,
615                    negated: *negated,
616                }))
617            }
618            _ => Ok(None),
619        }
620    }
621}
622
623impl Engine {
624    /// v7.29 (round-22 phase 3) — try to batch-evaluate a correlated
625    /// scalar subquery of the shape
626    ///   (SELECT expr FROM … WHERE inner_preds AND inner_col = outer_col
627    ///    [ORDER BY o [DESC]] [LIMIT 1])
628    /// by running the subquery ONCE without the correlation and
629    /// folding rows into a key→value map (group top-1 when ordered).
630    /// Returns None when the shape doesn't qualify; correctness then
631    /// falls back to per-row execution.
632    pub(crate) fn try_batch_correlated_scalar(
633        &self,
634        inner: &SelectStatement,
635        restrict: Option<(&[Row<'static>], &EvalContext<'_>)>,
636        cancel: CancelToken<'_>,
637    ) -> Result<Option<memoize::GroupMap>, EngineError> {
638        use spg_sql::ast::{BinOp, SelectItem as SI};
639        if !inner.ctes.is_empty()
640            || !inner.unions.is_empty()
641            || inner.group_by.is_some()
642            || inner.having.is_some()
643            || inner.distinct
644            || inner.items.len() != 1
645            || inner.order_by.len() > 1
646            || inner.offset.is_some()
647        {
648            return Ok(None);
649        }
650        // LIMIT must be absent or literally 1 (top-1 semantics).
651        if let Some(le) = inner.limit
652            && le.as_literal() != Some(1)
653        {
654            return Ok(None);
655        }
656        let Some(from) = &inner.from else {
657            return Ok(None);
658        };
659        if from.primary.lateral_subquery.is_some() || from.primary.unnest_expr.is_some() {
660            return Ok(None);
661        }
662        // Inner alias set.
663        let mut inner_aliases: Vec<String> = Vec::new();
664        inner_aliases.push(
665            from.primary
666                .alias
667                .clone()
668                .unwrap_or_else(|| from.primary.name.clone()),
669        );
670        for j in &from.joins {
671            if j.table.lateral_subquery.is_some() || j.table.unnest_expr.is_some() {
672                return Ok(None);
673            }
674            inner_aliases.push(
675                j.table
676                    .alias
677                    .clone()
678                    .unwrap_or_else(|| j.table.name.clone()),
679            );
680        }
681        let is_inner = |c: &spg_sql::ast::ColumnName| -> bool {
682            match &c.qualifier {
683                Some(q) => inner_aliases.iter().any(|a| a.eq_ignore_ascii_case(q)),
684                None => false,
685            }
686        };
687        let is_outer = |c: &spg_sql::ast::ColumnName| -> bool {
688            match &c.qualifier {
689                Some(q) => !inner_aliases.iter().any(|a| a.eq_ignore_ascii_case(q)),
690                // Synthetic group columns arrive bare after the
691                // aggregate rewrite.
692                None => c.name.starts_with("__grp_") || c.name.starts_with("__agg_"),
693            }
694        };
695        // Every expression OTHER than the correlation conjunct must be
696        // fully inner (qualified to inner aliases).
697        let all_inner = |e: &Expr| -> bool {
698            let mut cols: Vec<spg_sql::ast::ColumnName> = Vec::new();
699            let mut subs: Vec<&SelectStatement> = Vec::new();
700            visit_expr_columns_and_subqueries(e, &mut |c| cols.push(c.clone()), &mut |sub| {
701                subs.push(sub)
702            });
703            subs.is_empty() && cols.iter().all(|c| is_inner(c) && !c.name.is_empty())
704        };
705        let Some(w) = &inner.where_ else {
706            return Ok(None);
707        };
708        let conjuncts = reorder::split_and_conjunctions(w);
709        let mut corr: Option<(spg_sql::ast::ColumnName, spg_sql::ast::ColumnName)> = None; // (inner, outer)
710        let mut rest: Vec<&Expr> = Vec::new();
711        for c in conjuncts {
712            if let Expr::Binary {
713                lhs,
714                op: BinOp::Eq,
715                rhs,
716            } = c
717                && let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref())
718            {
719                let pair = if is_inner(a) && is_outer(b) {
720                    Some((a.clone(), b.clone()))
721                } else if is_inner(b) && is_outer(a) {
722                    Some((b.clone(), a.clone()))
723                } else {
724                    None
725                };
726                if let Some(p) = pair {
727                    if corr.is_some() {
728                        return Ok(None); // more than one correlation
729                    }
730                    corr = Some(p);
731                    continue;
732                }
733            }
734            if !all_inner(c) {
735                return Ok(None);
736            }
737            rest.push(c);
738        }
739        let Some((inner_col, outer_col)) = corr else {
740            return Ok(None);
741        };
742        let SI::Expr { expr: out_expr, .. } = &inner.items[0] else {
743            return Ok(None);
744        };
745        if !all_inner(out_expr) {
746            return Ok(None);
747        }
748        let order = inner.order_by.first();
749        if let Some(o) = order
750            && !all_inner(&o.expr)
751        {
752            return Ok(None);
753        }
754        // Build the batch statement: SELECT inner_col, [order], expr
755        // FROM … WHERE rest — no correlation, no order, no limit.
756        let mut batch = inner.clone();
757        batch.limit = None;
758        batch.offset = None;
759        batch.order_by = Vec::new();
760        batch.where_ = rest
761            .iter()
762            .map(|e| (*e).clone())
763            .reduce(|a, b| Expr::Binary {
764                lhs: alloc::boxed::Box::new(a),
765                op: BinOp::And,
766                rhs: alloc::boxed::Box::new(b),
767            });
768        let mut items: Vec<SI> = alloc::vec![SI::Expr {
769            expr: Expr::Column(inner_col.clone()),
770            alias: None,
771        }];
772        if let Some(o) = order {
773            items.push(SI::Expr {
774                expr: o.expr.clone(),
775                alias: None,
776            });
777        }
778        items.push(SI::Expr {
779            expr: out_expr.clone(),
780            alias: None,
781        });
782        batch.items = items;
783        // v7.37.x (docker-fair SCALARSQ-aggregate path) — when the
784        // inner output expression is an aggregate (e.g. `COUNT(*)`
785        // for the `(SELECT COUNT(*) FROM inner WHERE inner.k =
786        // outer.k)` scalar subquery shape), the batch query
787        // `SELECT inner.k, COUNT(*) FROM inner` is invalid SQL
788        // without `GROUP BY inner.k`. Inject the GROUP BY so the
789        // aggregate executor produces (key → count) pairs, matching
790        // the per-key scalar-subquery semantics. Pre-7.37.x this
791        // case mis-executed as a single anonymous group and either
792        // returned a wrong total or surfaced an `UnknownQualifier`
793        // (when the rewriter couldn't bind the bare column ref).
794        if aggregate::contains_aggregate(out_expr) {
795            batch.group_by = Some(alloc::vec![Expr::Column(inner_col.clone())]);
796        }
797        // v7.32 (architecture v2 P3) — keyed index-probe. When the
798        // caller hands a restriction set (the ≤LIMIT surviving outer
799        // rows of a post-LIMIT deferred subquery) AND the correlation
800        // column is backed by an index, evaluate only the surviving
801        // correlation keys via per-key index seek instead of scanning
802        // the whole inner relation. This is PG's SubPlan with an index
803        // scan: 50 seeks of ~µs each vs a 24k-row all-keys batch
804        // (~16 ms). The grouping below is shared — keyed result ≡
805        // full-batch result for the covered keys, so semantics are
806        // identical.
807        //
808        // The inner relation may itself be a join. The correlation
809        // column names the *driving* table; PG, MySQL and MariaDB all
810        // plan a correlated join subquery the same way — seek the
811        // correlation index, then index-nested-loop to the joined
812        // table. We promote that table to drive `batch` (an all-INNER
813        // chain only) so the per-key `inner_col = <lit>` predicate
814        // becomes a primary index seek and the existing INL path joins
815        // the rest. A correlation column without a usable index, or a
816        // join the promotion can't safely reorder, returns None and
817        // the caller falls back to the lazy all-keys batch (no
818        // regression).
819        let keyed: Option<(&[Row<'static>], &EvalContext<'_>)> =
820            restrict.and_then(|(rows, rctx)| {
821                // Resolve the table that owns the correlation column.
822                let driver_name: &str = if from.joins.is_empty() {
823                    from.primary.name.as_str()
824                } else {
825                    let q = inner_col.qualifier.as_deref()?;
826                    let primary_alias = from
827                        .primary
828                        .alias
829                        .as_deref()
830                        .unwrap_or(from.primary.name.as_str());
831                    if primary_alias.eq_ignore_ascii_case(q) {
832                        from.primary.name.as_str()
833                    } else {
834                        from.joins
835                            .iter()
836                            .find(|j| {
837                                j.table
838                                    .alias
839                                    .as_deref()
840                                    .unwrap_or(j.table.name.as_str())
841                                    .eq_ignore_ascii_case(q)
842                            })
843                            .map(|j| j.table.name.as_str())?
844                    }
845                };
846                let table = self.active_catalog().get(driver_name)?;
847                let pos = table
848                    .schema()
849                    .columns
850                    .iter()
851                    .position(|c| c.name.eq_ignore_ascii_case(&inner_col.name))?;
852                table.index_on(pos)?;
853                // v7.33 (mailrs 7.32.1) — cost guard. The keyed path runs one
854                // index seek (a full `exec_select_cancel` round trip) per
855                // surviving correlation key. That wins when few keys survive
856                // (a tight outer LIMIT leaves a handful), but a *correlated
857                // select-list subquery with no outer LIMIT* leaves every group
858                // alive — `restrict` is then all ~N groups, and N seeks dwarf
859                // a single grouped all-keys scan of the same driver. Reproduced
860                // on the conversation aggregation (`get_conversations_by_thread_ids`,
861                // no LIMIT): 24k per-key seeks took 78–155 ms vs ~one scan.
862                // Fall through to the all-keys batch (`keyed = None` → the
863                // `else` arm below) when the survivor set is large relative to
864                // the driver; the batch's group map ⊇ the keyed map for every
865                // covered key, so the result is identical. Crossover ~rows/4
866                // (measured per-seek exec overhead vs per-row scan cost).
867                if rows.len().saturating_mul(4) >= table.row_count() {
868                    return None;
869                }
870                // For a join inner, drive the seek from the correlation
871                // table so `inner_col = <lit>` lands as a primary index
872                // seek (else the source-order primary scans the full
873                // relation and the join hash-builds the whole peer — the
874                // 12 GB all-keys hog R30 hit at prod scale).
875                if !from.joins.is_empty() {
876                    let driver_alias = inner_col.qualifier.as_deref()?;
877                    if !reorder::drive_from(&mut batch, driver_alias) {
878                        return None;
879                    }
880                }
881                Some((rows, rctx))
882            });
883        let rows = if let Some((restrict_rows, rctx)) = keyed {
884            BATCHED_SCALAR_KEYED_FIRE_COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
885            // v7.37.4 A' — collect the deduped surviving correlation
886            // keys, then issue ONE `inner.k IN (lit1, …, litN)` probe
887            // instead of N separate `inner.k = lit` probes. The v7.34.3
888            // IN-list seek path treats the literal list as a bitmap-
889            // style index sweep (single index lookup per literal,
890            // unioned), so the total cost is O(N seeks + matched rows)
891            // — same asymptotic as the N-probe loop but without N
892            // rounds of stmt clone + plan + executor stack overhead.
893            //
894            // Per-probe overhead measured on mailrs prod 100k:
895            //   - sequential: 50 probes × ~1.7 ms = ~85 ms per subq
896            //   - 3 subqueries × ~85 ms = ~255 ms of the 388 ms total
897            // IN-list batched probe is one stmt + N IN-list literals,
898            // amortising the plan + setup over all keys.
899            let mut seen: alloc::collections::BTreeSet<String> =
900                alloc::collections::BTreeSet::new();
901            let mut key_lits: Vec<Expr> = Vec::new();
902            for srow in restrict_rows {
903                cancel.check()?;
904                let kv = eval::eval_expr(&Expr::Column(outer_col.clone()), srow, rctx)
905                    .map_err(EngineError::Eval)?;
906                if matches!(kv, Value::Null) {
907                    continue;
908                }
909                if !seen.insert(aggregate::encode_key(core::slice::from_ref(&kv))) {
910                    continue;
911                }
912                key_lits.push(value_to_literal_expr(kv)?);
913            }
914            if key_lits.is_empty() {
915                Vec::new()
916            } else {
917                let in_pred = Expr::InList {
918                    expr: alloc::boxed::Box::new(Expr::Column(inner_col.clone())),
919                    list: key_lits,
920                    negated: false,
921                };
922                let mut probe = batch.clone();
923                probe.where_ = Some(match probe.where_.take() {
924                    Some(w) => Expr::Binary {
925                        lhs: alloc::boxed::Box::new(w),
926                        op: BinOp::And,
927                        rhs: alloc::boxed::Box::new(in_pred),
928                    },
929                    None => in_pred,
930                });
931                BATCHED_SCALAR_KEYED_PROBE_COUNT
932                    .fetch_add(1, core::sync::atomic::Ordering::Relaxed);
933                if let QueryResult::Rows { rows, .. } = self.exec_select_cancel(&probe, cancel)? {
934                    rows
935                } else {
936                    Vec::new()
937                }
938            }
939        } else {
940            BATCHED_SCALAR_FALL_THROUGH_COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
941            let r = self.exec_select_cancel(&batch, cancel)?;
942            let QueryResult::Rows { rows, .. } = r else {
943                return Ok(None);
944            };
945            rows
946        };
947        let has_order = order.is_some();
948        let (desc, nf) = order
949            .map(|o| (o.desc, o.nulls_first))
950            .unwrap_or((false, None));
951        let mut best: alloc::collections::BTreeMap<String, (Option<Value>, Value)> =
952            alloc::collections::BTreeMap::new();
953        for row in rows {
954            let key_v = row.values.first().cloned().unwrap_or(Value::Null);
955            if matches!(key_v, Value::Null) {
956                continue;
957            }
958            let key = aggregate::encode_key(core::slice::from_ref(&key_v));
959            let (ord_v, out_v) = if has_order {
960                (
961                    Some(row.values.get(1).cloned().unwrap_or(Value::Null)),
962                    row.values.get(2).cloned().unwrap_or(Value::Null),
963                )
964            } else {
965                (None, row.values.get(1).cloned().unwrap_or(Value::Null))
966            };
967            match best.get(&key) {
968                None => {
969                    best.insert(key, (ord_v, out_v));
970                }
971                Some((cur_ord, _)) if has_order => {
972                    // The sorted-first row wins: candidate beats the
973                    // incumbent when it compares LESS under the key's
974                    // ordering.
975                    let cand = ord_v.clone().unwrap_or(Value::Null);
976                    let cur = cur_ord.clone().unwrap_or(Value::Null);
977                    if order_by_value_cmp(desc, nf, &cand, &cur) == core::cmp::Ordering::Less {
978                        best.insert(key, (ord_v, out_v));
979                    }
980                }
981                Some(_) => {} // unordered: first row stands (any row is valid)
982            }
983        }
984        let map = best.into_iter().map(|(k, (_, v))| (k, v)).collect();
985        // v7.37.x (docker-fair SCALARSQ attack) — empty-default per
986        // PG scalar-subquery aggregate semantics. Captured here so the
987        // splice path doesn't have to re-introspect a possibly-hollowed
988        // inner template.
989        let empty_default = scalar_subquery_empty_default(inner);
990        Ok(Some((outer_col, map, empty_default)))
991    }
992}
993
994impl Engine {
995    /// v7.34 (mailrs conn-pool-exhaustion P0) — decorrelate a correlated
996    /// `[NOT] EXISTS` into a hash semi/anti-join. Recognise
997    ///   EXISTS (SELECT … FROM t [joins]
998    ///           WHERE k1 = o1 AND … AND kN = oN AND <inner-preds>)
999    /// run the inner ONCE without the correlation, collect the set of
1000    /// inner key-tuples `(k1,…,kN)` that satisfy the inner-preds; an outer
1001    /// row's EXISTS then reduces to a membership test on `(o1,…,oN)`. The
1002    /// reported `count_unseen` ran two correlated `NOT EXISTS` per ~24k
1003    /// join survivors (~48k inner executions, 98.7% of a 1.4 s query);
1004    /// this turns each into one scan + 24k lookups.
1005    ///
1006    /// Multi-column correlation is supported (the prod `snoozed` anti-join
1007    /// correlates on both `thread_id` and `account_address`). NULL is
1008    /// exact: an outer key with any NULL component is never present
1009    /// (`NULL = k` is never true), so EXISTS=false / NOT EXISTS=true,
1010    /// identical to the per-row resolver. Returns None when the shape
1011    /// doesn't qualify — the caller falls back to per-row execution, so
1012    /// there is no regression.
1013    pub(crate) fn try_batch_correlated_exists(
1014        &self,
1015        inner: &SelectStatement,
1016        cancel: CancelToken<'_>,
1017    ) -> Result<Option<memoize::ExistsSet>, EngineError> {
1018        use spg_sql::ast::SelectItem as SI;
1019        if !inner.ctes.is_empty()
1020            || !inner.unions.is_empty()
1021            || inner.group_by.is_some()
1022            || inner.having.is_some()
1023            || inner.distinct
1024        {
1025            return Ok(None);
1026        }
1027        let Some(from) = &inner.from else {
1028            return Ok(None);
1029        };
1030        if from.primary.lateral_subquery.is_some()
1031            || from.primary.unnest_expr.is_some()
1032            || from.primary.generate_series_args.is_some()
1033            || from.primary.as_of_segment.is_some()
1034        {
1035            return Ok(None);
1036        }
1037        let mut inner_aliases: Vec<String> = Vec::new();
1038        inner_aliases.push(
1039            from.primary
1040                .alias
1041                .clone()
1042                .unwrap_or_else(|| from.primary.name.clone()),
1043        );
1044        for j in &from.joins {
1045            if j.table.lateral_subquery.is_some() || j.table.unnest_expr.is_some() {
1046                return Ok(None);
1047            }
1048            inner_aliases.push(
1049                j.table
1050                    .alias
1051                    .clone()
1052                    .unwrap_or_else(|| j.table.name.clone()),
1053            );
1054        }
1055        let is_inner = |c: &spg_sql::ast::ColumnName| -> bool {
1056            match &c.qualifier {
1057                Some(q) => inner_aliases.iter().any(|a| a.eq_ignore_ascii_case(q)),
1058                None => false,
1059            }
1060        };
1061        let is_outer = |c: &spg_sql::ast::ColumnName| -> bool {
1062            match &c.qualifier {
1063                Some(q) => !inner_aliases.iter().any(|a| a.eq_ignore_ascii_case(q)),
1064                None => c.name.starts_with("__grp_") || c.name.starts_with("__agg_"),
1065            }
1066        };
1067        let all_inner = |e: &Expr| -> bool {
1068            let mut cols: Vec<spg_sql::ast::ColumnName> = Vec::new();
1069            let mut subs: Vec<&SelectStatement> = Vec::new();
1070            visit_expr_columns_and_subqueries(e, &mut |c| cols.push(c.clone()), &mut |sub| {
1071                subs.push(sub)
1072            });
1073            subs.is_empty() && cols.iter().all(|c| is_inner(c) && !c.name.is_empty())
1074        };
1075        let Some(w) = &inner.where_ else {
1076            return Ok(None);
1077        };
1078        let conjuncts = reorder::split_and_conjunctions(w);
1079        let mut inner_keys: Vec<spg_sql::ast::ColumnName> = Vec::new();
1080        let mut outer_cols: Vec<spg_sql::ast::ColumnName> = Vec::new();
1081        let mut rest: Vec<&Expr> = Vec::new();
1082        for c in conjuncts {
1083            if let Expr::Binary {
1084                lhs,
1085                op: BinOp::Eq,
1086                rhs,
1087            } = c
1088                && let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref())
1089            {
1090                let pair = if is_inner(a) && is_outer(b) {
1091                    Some((a.clone(), b.clone()))
1092                } else if is_inner(b) && is_outer(a) {
1093                    Some((b.clone(), a.clone()))
1094                } else {
1095                    None
1096                };
1097                if let Some((ic, oc)) = pair {
1098                    inner_keys.push(ic);
1099                    outer_cols.push(oc);
1100                    continue;
1101                }
1102            }
1103            // A non-correlation conjunct must be purely inner (carried
1104            // into the build scan). Anything else (outer-only filter,
1105            // mixed expression) is beyond this rewrite.
1106            if !all_inner(c) {
1107                return Ok(None);
1108            }
1109            rest.push(c);
1110        }
1111        if inner_keys.is_empty() {
1112            return Ok(None); // uncorrelated — materialised elsewhere
1113        }
1114        // Build: SELECT k1,…,kN FROM <inner from> WHERE <rest> — no
1115        // correlation, no order/limit. The inner relation may be a join;
1116        // exec handles it.
1117        let mut batch = inner.clone();
1118        batch.limit = None;
1119        batch.offset = None;
1120        batch.order_by = Vec::new();
1121        batch.distinct = false;
1122        batch.where_ = rest
1123            .iter()
1124            .map(|e| (*e).clone())
1125            .reduce(|a, b| Expr::Binary {
1126                lhs: alloc::boxed::Box::new(a),
1127                op: BinOp::And,
1128                rhs: alloc::boxed::Box::new(b),
1129            });
1130        batch.items = inner_keys
1131            .iter()
1132            .map(|c| SI::Expr {
1133                expr: Expr::Column(c.clone()),
1134                alias: None,
1135            })
1136            .collect();
1137        let r = self.exec_select_cancel(&batch, cancel)?;
1138        let QueryResult::Rows { rows, .. } = r else {
1139            return Ok(None);
1140        };
1141        let n = inner_keys.len();
1142        let mut set: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
1143        for row in rows {
1144            let keys = row.values.get(..n).unwrap_or(&row.values);
1145            // A NULL key component can never satisfy `k = outer`, so the
1146            // tuple matches no outer row — drop it from the set.
1147            if keys.iter().any(|v| matches!(v, Value::Null)) {
1148                continue;
1149            }
1150            set.insert(aggregate::encode_key(keys));
1151        }
1152        Ok(Some((outer_cols, set)))
1153    }
1154}
1155
1156impl Engine {
1157    /// v7.33 (mailrs 7.32.1) — sublink pull-up for aggregate-wrapped
1158    /// correlated scalar subqueries. Rewrite
1159    ///   AGG( (SELECT j_col FROM t j WHERE j.key = outer.col [AND inner preds]) )
1160    /// into a LEFT JOIN plus a plain column reference:
1161    ///   AGG(j.j_col) … LEFT JOIN t AS j ON j.key = outer.col [AND inner preds]
1162    /// when `t.key` carries a single-column UNIQUE / PRIMARY KEY constraint.
1163    /// That constraint guarantees the join matches AT MOST ONE inner row
1164    /// per outer row, which is exactly the scalar subquery's at-most-one
1165    /// contract (NULL on no match), so the aggregate folds an identical
1166    /// per-row value stream — only now the executor streams one join
1167    /// instead of splicing a per-row subplan (the R31 path cloned a hollow
1168    /// template per outer row: ~24k clones for the mailrs conversation
1169    /// aggregation).
1170    ///
1171    /// Scoped tightly for safety: the subquery must sit inside an aggregate
1172    /// argument (so the joined column is always folded, never a bare
1173    /// select-list column a GROUP BY would reject); the inner must be a
1174    /// single plain-table scan projecting one inner column with exactly one
1175    /// `inner.key = outer.col` correlation (both qualified) plus optional
1176    /// all-inner predicates; and the select list must have no bare wildcard
1177    /// (a join would widen `*`). Anything else is left for the existing
1178    /// per-row / batch resolver. Returns true when it rewrote at least one.
1179    /// v7.37.4 (A — correlated LIMIT 1 ORDER BY DESC subquery pullup) —
1180    /// plan-time rewrite of the "per-key latest" select-list scalar
1181    /// subquery pattern:
1182    ///
1183    /// ```sql
1184    /// SELECT outer.k,
1185    ///        (SELECT proj_expr FROM inner
1186    ///          WHERE inner.k = outer.k AND <non_corr_preds>
1187    ///          ORDER BY sort_key DESC LIMIT 1) AS latest_proj
1188    ///   FROM outer
1189    /// ```
1190    ///
1191    /// becomes (semantically equivalent, executor-friendly):
1192    ///
1193    /// ```sql
1194    /// WITH __cl1_N AS (
1195    ///   SELECT inner.k AS jk,
1196    ///          (array_agg(proj_expr ORDER BY sort_key DESC NULLS LAST))[1] AS pj
1197    ///     FROM <inner.from>
1198    ///    WHERE <non_corr_preds>
1199    ///    GROUP BY inner.k
1200    /// )
1201    /// SELECT outer.k, MAX(__cl1_N.pj) AS latest_proj
1202    ///   FROM outer LEFT JOIN __cl1_N ON __cl1_N.jk = outer.k
1203    /// ```
1204    ///
1205    /// The CTE materialises once for the whole outer scan; LEFT JOIN
1206    /// on the GROUP-BY-unique `jk` column never multiplies outer rows.
1207    /// The `array_agg(... ORDER BY ...)[1]` form reuses the v7.33
1208    /// `first_ordered` argmax executor (per-group keep the first row,
1209    /// no array build).
1210    ///
1211    /// Common shape across inbox / feed / timeline applications:
1212    /// thread latest message, user latest transaction, device latest
1213    /// heartbeat. **Not a mailrs-specific patch** — any client query
1214    /// in this shape gets the rewrite.
1215    ///
1216    /// Acceptance (`try_pull_up_limit_one`):
1217    /// - inner: single SELECT, LIMIT 1 + ORDER BY <expr>, no GROUP BY /
1218    ///   HAVING / DISTINCT / CTE / UNION / OFFSET, single projection
1219    /// - inner FROM: may contain JOINs (INNER) over plain tables; no
1220    ///   LATERAL / UNNEST / generate_series / AS OF; no outer reference
1221    ///   inside join ON
1222    /// - WHERE: exactly one `inner.k = outer.col` (qualified columns)
1223    ///   + non-correlated all-inner predicates
1224    /// - projection: scalar expression, no aggregates / windows
1225    /// - outer: SelectStatement with FROM, no wildcards
1226    ///
1227    /// Returns true when at least one ScalarSubquery was rewritten.
1228    /// Returns false (no-op) when nothing in the statement matches —
1229    /// the existing per-row resolver then handles whatever's left.
1230    pub(crate) fn pull_up_correlated_limit_one_subqueries(
1231        &self,
1232        stmt: &mut SelectStatement,
1233    ) -> bool {
1234        // Phase 5 differential knob: an `AtomicBool` switch will land
1235        // alongside the byte-equal differential e2e (no_std rules out
1236        // std::env::var here). Production keeps the pass default-on.
1237        //
1238        // Outer FROM required (no FROM → nothing to JOIN against);
1239        // outer wildcards (`SELECT *`) widen the projection and would
1240        // surface the joined CTE's columns — refuse for safety.
1241        if stmt.from.is_none() || stmt.items.iter().any(|i| matches!(i, SelectItem::Wildcard)) {
1242            return false;
1243        }
1244        // Aliases an outer-correlation column may qualify to. Same
1245        // collection rule as `pull_up_unique_correlated_agg_subqueries`.
1246        let outer_aliases: alloc::collections::BTreeSet<String> = {
1247            let from = stmt.from.as_ref().expect("from present");
1248            let mut s = alloc::collections::BTreeSet::new();
1249            let push = |s: &mut alloc::collections::BTreeSet<String>, t: &TableRef| {
1250                s.insert(
1251                    t.alias
1252                        .clone()
1253                        .unwrap_or_else(|| t.name.clone())
1254                        .to_ascii_lowercase(),
1255                );
1256            };
1257            push(&mut s, &from.primary);
1258            for j in &from.joins {
1259                push(&mut s, &j.table);
1260            }
1261            s
1262        };
1263        let outer_has_group_by = stmt.group_by.is_some() || stmt.group_by_all;
1264        let mut new_ctes: Vec<Cte> = Vec::new();
1265        let mut new_joins: Vec<FromJoin> = Vec::new();
1266        let cte_seed = stmt.ctes.len();
1267        for item in &mut stmt.items {
1268            if let SelectItem::Expr { expr, .. } = item {
1269                self.pull_up_walk_limit_one(
1270                    expr,
1271                    false,
1272                    &outer_aliases,
1273                    outer_has_group_by,
1274                    cte_seed,
1275                    &mut new_ctes,
1276                    &mut new_joins,
1277                );
1278            }
1279        }
1280        if new_ctes.is_empty() {
1281            return false;
1282        }
1283        PULLUP_LIMIT1_FIRE_COUNT
1284            .fetch_add(new_ctes.len() as u64, core::sync::atomic::Ordering::Relaxed);
1285        stmt.ctes.extend(new_ctes);
1286        stmt.from
1287            .as_mut()
1288            .expect("from present")
1289            .joins
1290            .extend(new_joins);
1291        true
1292    }
1293
1294    /// v7.37.4 — recursive mutable walk over a select-list expression
1295    /// for the LIMIT 1 pullup. Tracks `in_agg` so a ScalarSubquery
1296    /// already inside an aggregate doesn't get a redundant MAX wrapper
1297    /// (the outer aggregate folds whatever cell value the join supplies).
1298    #[allow(clippy::too_many_arguments)]
1299    fn pull_up_walk_limit_one(
1300        &self,
1301        e: &mut Expr,
1302        in_agg: bool,
1303        outer_aliases: &alloc::collections::BTreeSet<String>,
1304        outer_has_group_by: bool,
1305        cte_seed: usize,
1306        ctes_out: &mut Vec<Cte>,
1307        joins_out: &mut Vec<FromJoin>,
1308    ) {
1309        match e {
1310            Expr::ScalarSubquery(inner) => {
1311                if let Some((cte, join, cte_col)) =
1312                    self.try_pull_up_limit_one(inner, outer_aliases, cte_seed + ctes_out.len())
1313                {
1314                    ctes_out.push(cte);
1315                    joins_out.push(join);
1316                    // Outer needs a single scalar per outer row. With a
1317                    // LEFT JOIN against the CTE (sq.jk UNIQUE by GROUP
1318                    // BY), sq.pj is functionally a single value per
1319                    // join key — but a strict GROUP BY checker won't
1320                    // know that. When the outer query has its own
1321                    // GROUP BY and this position isn't already wrapped
1322                    // in an aggregate, wrap in MAX(sq.pj) so the
1323                    // checker sees an aggregate; MAX over a single
1324                    // value equals the value (any aggregate would).
1325                    let col_expr = Expr::Column(cte_col);
1326                    *e = if outer_has_group_by && !in_agg {
1327                        Expr::FunctionCall {
1328                            name: "max".into(),
1329                            args: alloc::vec![col_expr],
1330                        }
1331                    } else {
1332                        col_expr
1333                    };
1334                }
1335                // Otherwise leave for the existing per-row resolver.
1336                // The subquery body is a separate scope — don't descend.
1337            }
1338            Expr::FunctionCall { name, args } => {
1339                let child = in_agg || aggregate::is_aggregate_name(name);
1340                for a in args.iter_mut() {
1341                    self.pull_up_walk_limit_one(
1342                        a,
1343                        child,
1344                        outer_aliases,
1345                        outer_has_group_by,
1346                        cte_seed,
1347                        ctes_out,
1348                        joins_out,
1349                    );
1350                }
1351            }
1352            Expr::AggregateOrdered {
1353                call,
1354                order_by,
1355                filter,
1356                ..
1357            } => {
1358                self.pull_up_walk_limit_one(
1359                    call,
1360                    true,
1361                    outer_aliases,
1362                    outer_has_group_by,
1363                    cte_seed,
1364                    ctes_out,
1365                    joins_out,
1366                );
1367                for o in order_by.iter_mut() {
1368                    self.pull_up_walk_limit_one(
1369                        &mut o.expr,
1370                        true,
1371                        outer_aliases,
1372                        outer_has_group_by,
1373                        cte_seed,
1374                        ctes_out,
1375                        joins_out,
1376                    );
1377                }
1378                if let Some(f) = filter {
1379                    self.pull_up_walk_limit_one(
1380                        f,
1381                        true,
1382                        outer_aliases,
1383                        outer_has_group_by,
1384                        cte_seed,
1385                        ctes_out,
1386                        joins_out,
1387                    );
1388                }
1389            }
1390            Expr::Binary { lhs, rhs, .. } => {
1391                self.pull_up_walk_limit_one(
1392                    lhs,
1393                    in_agg,
1394                    outer_aliases,
1395                    outer_has_group_by,
1396                    cte_seed,
1397                    ctes_out,
1398                    joins_out,
1399                );
1400                self.pull_up_walk_limit_one(
1401                    rhs,
1402                    in_agg,
1403                    outer_aliases,
1404                    outer_has_group_by,
1405                    cte_seed,
1406                    ctes_out,
1407                    joins_out,
1408                );
1409            }
1410            Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
1411                self.pull_up_walk_limit_one(
1412                    expr,
1413                    in_agg,
1414                    outer_aliases,
1415                    outer_has_group_by,
1416                    cte_seed,
1417                    ctes_out,
1418                    joins_out,
1419                );
1420            }
1421            Expr::Like { expr, pattern, .. } => {
1422                self.pull_up_walk_limit_one(
1423                    expr,
1424                    in_agg,
1425                    outer_aliases,
1426                    outer_has_group_by,
1427                    cte_seed,
1428                    ctes_out,
1429                    joins_out,
1430                );
1431                self.pull_up_walk_limit_one(
1432                    pattern,
1433                    in_agg,
1434                    outer_aliases,
1435                    outer_has_group_by,
1436                    cte_seed,
1437                    ctes_out,
1438                    joins_out,
1439                );
1440            }
1441            Expr::InList { expr, list, .. } => {
1442                self.pull_up_walk_limit_one(
1443                    expr,
1444                    in_agg,
1445                    outer_aliases,
1446                    outer_has_group_by,
1447                    cte_seed,
1448                    ctes_out,
1449                    joins_out,
1450                );
1451                for it in list.iter_mut() {
1452                    self.pull_up_walk_limit_one(
1453                        it,
1454                        in_agg,
1455                        outer_aliases,
1456                        outer_has_group_by,
1457                        cte_seed,
1458                        ctes_out,
1459                        joins_out,
1460                    );
1461                }
1462            }
1463            Expr::Case {
1464                operand,
1465                branches,
1466                else_branch,
1467            } => {
1468                if let Some(o) = operand {
1469                    self.pull_up_walk_limit_one(
1470                        o,
1471                        in_agg,
1472                        outer_aliases,
1473                        outer_has_group_by,
1474                        cte_seed,
1475                        ctes_out,
1476                        joins_out,
1477                    );
1478                }
1479                for (w, t) in branches.iter_mut() {
1480                    self.pull_up_walk_limit_one(
1481                        w,
1482                        in_agg,
1483                        outer_aliases,
1484                        outer_has_group_by,
1485                        cte_seed,
1486                        ctes_out,
1487                        joins_out,
1488                    );
1489                    self.pull_up_walk_limit_one(
1490                        t,
1491                        in_agg,
1492                        outer_aliases,
1493                        outer_has_group_by,
1494                        cte_seed,
1495                        ctes_out,
1496                        joins_out,
1497                    );
1498                }
1499                if let Some(eb) = else_branch {
1500                    self.pull_up_walk_limit_one(
1501                        eb,
1502                        in_agg,
1503                        outer_aliases,
1504                        outer_has_group_by,
1505                        cte_seed,
1506                        ctes_out,
1507                        joins_out,
1508                    );
1509                }
1510            }
1511            // Same boundary policy as `pull_up_walk` — don't descend
1512            // into window calls, EXISTS, etc.
1513            _ => {}
1514        }
1515    }
1516
1517    /// v7.37.4 — decide whether a correlated scalar subquery qualifies
1518    /// for the LIMIT 1 → CTE pullup. Returns the CTE to add to outer
1519    /// `WITH`, the LEFT JOIN to append, and the (qualified) column
1520    /// that replaces the subquery node. None means: leave it for the
1521    /// per-row resolver.
1522    fn try_pull_up_limit_one(
1523        &self,
1524        inner: &SelectStatement,
1525        outer_aliases: &alloc::collections::BTreeSet<String>,
1526        alias_n: usize,
1527    ) -> Option<(Cte, FromJoin, ColumnName)> {
1528        // v7.37.4 A phase-2 finding (2026-06-19): the CTE rewrite
1529        // fires correctly on the mailrs prod subq 3 shape (verified
1530        // via PULLUP_LIMIT1_FIRE_COUNT in `pullup_fires_on_mailrs_subq3_shape`)
1531        // but PRODUCES A REGRESSION on the full prod SQL — mini cold
1532        // 100k SPGE 388.5 → 523.8 ms (+35%). Root cause:
1533        //   1. SPG's existing `try_batch_correlated_scalar` already
1534        //      handles the LIMIT 1 + ORDER BY 1 shape via post-LIMIT
1535        //      defer + keyed index seek (~ µs per surfaced outer key).
1536        //   2. The CTE form forces a full inner-table GROUP BY scan
1537        //      (~ 100 ms for 100k messages), then exec_with_ctes
1538        //      strips ctes + re-enters the body — extra catalog
1539        //      clone + double scan.
1540        //   3. Outer LIMIT 50 + GROUP BY thread_id means only ~50
1541        //      outer keys ultimately matter; CTE pre-aggregates ALL
1542        //      keys eagerly, wasting work for the unsurfaced 99 %.
1543        //
1544        // The CTE rewrite is right shape FOR the wrong root cause.
1545        // Real ceiling-first target is to make the existing batch
1546        // resolver's keyed-restriction path fire for the mailrs
1547        // GROUP BY + LIMIT shape, not to bypass it with a CTE.
1548        //
1549        // Keep the implementation dormant — the walker + gate
1550        // analysis stays as reference; turning this back on requires
1551        // a cost gate that proves CTE materialise + LEFT JOIN beats
1552        // the batch resolver for the SHAPE AT HAND (rare in practice).
1553        return None;
1554        #[allow(unreachable_code)]
1555        // Inner shape gates.
1556        if !inner.ctes.is_empty()
1557            || !inner.unions.is_empty()
1558            || inner.group_by.is_some()
1559            || inner.group_by_all
1560            || inner.having.is_some()
1561            || inner.distinct
1562            || inner.offset.is_some()
1563            || inner.items.len() != 1
1564            || inner.order_by.is_empty()
1565        {
1566            return None;
1567        }
1568        // LIMIT must be the literal 1 (placeholders bind late; we
1569        // can't guarantee the value here).
1570        match inner.limit {
1571            Some(LimitExpr::Literal(1)) => {}
1572            _ => return None,
1573        }
1574        let from = inner.from.as_ref()?;
1575        // Phase 2: single plain-table inner. Phase 3 lifts this gate
1576        // to allow inner INNER JOINs whose ON clauses are all-inner.
1577        if !from.joins.is_empty()
1578            || from.primary.lateral_subquery.is_some()
1579            || from.primary.unnest_expr.is_some()
1580            || from.primary.generate_series_args.is_some()
1581            || from.primary.as_of_segment.is_some()
1582        {
1583            return None;
1584        }
1585        let inner_table = from.primary.name.clone();
1586        let inner_alias = from
1587            .primary
1588            .alias
1589            .clone()
1590            .unwrap_or_else(|| inner_table.clone());
1591        let is_inner = |c: &ColumnName| -> bool {
1592            c.qualifier
1593                .as_deref()
1594                .is_some_and(|q| q.eq_ignore_ascii_case(&inner_alias))
1595        };
1596        let is_outer = |c: &ColumnName| -> bool {
1597            c.qualifier
1598                .as_deref()
1599                .is_some_and(|q| outer_aliases.contains(&q.to_ascii_lowercase()))
1600        };
1601        // Projection: scalar expression; reject aggregates / windows /
1602        // nested subqueries / outer references (the pulled-up SELECT
1603        // is uncorrelated GROUP BY — an outer column reference would
1604        // dangle).
1605        let SelectItem::Expr {
1606            expr: proj_expr,
1607            alias: _,
1608        } = &inner.items[0]
1609        else {
1610            return None;
1611        };
1612        if proj_has_disqualifying_shape(proj_expr, &inner_alias, outer_aliases) {
1613            return None;
1614        }
1615        // WHERE: exactly one `inner.k = outer.col`, plus all-inner
1616        // residual predicates.
1617        let where_ = inner.where_.as_ref()?;
1618        let mut corr: Option<(String, ColumnName)> = None;
1619        let mut non_corr: Vec<Expr> = Vec::new();
1620        for c in reorder::split_and_conjunctions(where_) {
1621            if let Expr::Binary {
1622                lhs,
1623                op: BinOp::Eq,
1624                rhs,
1625            } = c
1626                && let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref())
1627            {
1628                let pair = if is_inner(a) && is_outer(b) {
1629                    Some((a.name.clone(), b.clone()))
1630                } else if is_inner(b) && is_outer(a) {
1631                    Some((b.name.clone(), a.clone()))
1632                } else {
1633                    None
1634                };
1635                if let Some(p) = pair {
1636                    if corr.is_some() {
1637                        return None; // more than one correlation key
1638                    }
1639                    corr = Some(p);
1640                    continue;
1641                }
1642            }
1643            if !expr_is_all_inner(c, &inner_alias) {
1644                return None;
1645            }
1646            non_corr.push(c.clone());
1647        }
1648        let (inner_key, outer_col) = corr?;
1649        // ORDER BY: every key must be all-inner. Outer-referencing
1650        // sort keys would dangle after pullup.
1651        for ob in &inner.order_by {
1652            if !expr_is_all_inner(&ob.expr, &inner_alias) {
1653                return None;
1654            }
1655        }
1656        // Proj must also be all-inner (uncorrelated CTE body).
1657        if !expr_is_all_inner(proj_expr, &inner_alias) {
1658            return None;
1659        }
1660        // Build the CTE body:
1661        //   SELECT <inner.k> AS jk,
1662        //          (array_agg(<proj> ORDER BY <sort_keys>))[1] AS pj
1663        //     FROM <inner.from> WHERE <non_corr_AND_chain>
1664        //    GROUP BY <inner.k>
1665        let cte_name = alloc::format!("__cl1_{alias_n}");
1666        let jk_expr = Expr::Column(ColumnName {
1667            qualifier: Some(inner_alias.clone()),
1668            name: inner_key.clone(),
1669        });
1670        let argmax = Expr::ArraySubscript {
1671            target: alloc::boxed::Box::new(Expr::AggregateOrdered {
1672                call: alloc::boxed::Box::new(Expr::FunctionCall {
1673                    name: "array_agg".into(),
1674                    args: alloc::vec![proj_expr.clone()],
1675                }),
1676                order_by: inner.order_by.clone(),
1677                distinct: false,
1678                filter: None,
1679            }),
1680            index: alloc::boxed::Box::new(Expr::Literal(Literal::Integer(1))),
1681        };
1682        let body_where = if non_corr.is_empty() {
1683            None
1684        } else {
1685            let mut iter = non_corr.into_iter();
1686            let head = iter.next().expect("non_corr nonempty in this branch");
1687            Some(iter.fold(head, |acc, p| Expr::Binary {
1688                lhs: alloc::boxed::Box::new(acc),
1689                op: BinOp::And,
1690                rhs: alloc::boxed::Box::new(p),
1691            }))
1692        };
1693        let body = SelectStatement {
1694            ctes: Vec::new(),
1695            distinct: false,
1696            items: alloc::vec![
1697                SelectItem::Expr {
1698                    expr: jk_expr.clone(),
1699                    alias: Some("jk".into()),
1700                },
1701                SelectItem::Expr {
1702                    expr: argmax,
1703                    alias: Some("pj".into()),
1704                },
1705            ],
1706            from: Some(from.clone()),
1707            where_: body_where,
1708            group_by: Some(alloc::vec![jk_expr]),
1709            group_by_all: false,
1710            having: None,
1711            unions: Vec::new(),
1712            order_by: Vec::new(),
1713            limit: None,
1714            offset: None,
1715            limit_with_ties: false,
1716        };
1717        let cte = Cte {
1718            name: cte_name.clone(),
1719            body: spg_sql::ast::CteBody::Select(body),
1720            recursive: false,
1721            column_overrides: Vec::new(),
1722        };
1723        // LEFT JOIN __cl1_N ON __cl1_N.jk = <outer_col>
1724        let join = FromJoin {
1725            kind: JoinKind::Left,
1726            table: TableRef {
1727                name: cte_name.clone(),
1728                alias: None,
1729                as_of_segment: None,
1730                unnest_expr: None,
1731                unnest_column_aliases: Vec::new(),
1732                generate_series_args: None,
1733                lateral_subquery: None,
1734                jsonb_each_text_arg: None,
1735            },
1736            on: Some(Expr::Binary {
1737                lhs: alloc::boxed::Box::new(Expr::Column(ColumnName {
1738                    qualifier: Some(cte_name.clone()),
1739                    name: "jk".into(),
1740                })),
1741                op: BinOp::Eq,
1742                rhs: alloc::boxed::Box::new(Expr::Column(outer_col)),
1743            }),
1744        };
1745        let repl = ColumnName {
1746            qualifier: Some(cte_name),
1747            name: "pj".into(),
1748        };
1749        Some((cte, join, repl))
1750    }
1751
1752    pub(crate) fn pull_up_unique_correlated_agg_subqueries(
1753        &self,
1754        stmt: &mut SelectStatement,
1755    ) -> bool {
1756        if stmt.from.is_none() || stmt.items.iter().any(|i| matches!(i, SelectItem::Wildcard)) {
1757            return false;
1758        }
1759        // Aliases an outer-correlation column may qualify to.
1760        let outer_aliases: alloc::collections::BTreeSet<String> = {
1761            let from = stmt.from.as_ref().expect("from present");
1762            let mut s = alloc::collections::BTreeSet::new();
1763            let push = |s: &mut alloc::collections::BTreeSet<String>, t: &TableRef| {
1764                s.insert(
1765                    t.alias
1766                        .clone()
1767                        .unwrap_or_else(|| t.name.clone())
1768                        .to_ascii_lowercase(),
1769                );
1770            };
1771            push(&mut s, &from.primary);
1772            for j in &from.joins {
1773                push(&mut s, &j.table);
1774            }
1775            s
1776        };
1777        let mut new_joins: Vec<FromJoin> = Vec::new();
1778        for item in &mut stmt.items {
1779            if let SelectItem::Expr { expr, .. } = item {
1780                self.pull_up_walk(expr, false, &outer_aliases, &mut new_joins);
1781            }
1782        }
1783        if new_joins.is_empty() {
1784            return false;
1785        }
1786        stmt.from
1787            .as_mut()
1788            .expect("from present")
1789            .joins
1790            .extend(new_joins);
1791        true
1792    }
1793
1794    /// Recursive mutable walk over an expression tracking whether we are
1795    /// inside an aggregate argument. A correlated scalar subquery found in
1796    /// aggregate context that `try_pull_up_join` accepts is replaced in
1797    /// place by the joined column; the join is queued in `joins_out`.
1798    fn pull_up_walk(
1799        &self,
1800        e: &mut Expr,
1801        in_agg: bool,
1802        outer_aliases: &alloc::collections::BTreeSet<String>,
1803        joins_out: &mut Vec<FromJoin>,
1804    ) {
1805        match e {
1806            Expr::ScalarSubquery(inner) => {
1807                if in_agg
1808                    && let Some((join, col)) =
1809                        self.try_pull_up_join(inner, outer_aliases, joins_out.len())
1810                {
1811                    joins_out.push(join);
1812                    *e = Expr::Column(col);
1813                }
1814                // Otherwise leave for the existing resolver; the subquery
1815                // body is a separate scope, so don't descend into it.
1816            }
1817            Expr::FunctionCall { name, args } => {
1818                let child = in_agg || aggregate::is_aggregate_name(name);
1819                for a in args.iter_mut() {
1820                    self.pull_up_walk(a, child, outer_aliases, joins_out);
1821                }
1822            }
1823            Expr::AggregateOrdered {
1824                call,
1825                order_by,
1826                filter,
1827                ..
1828            } => {
1829                self.pull_up_walk(call, true, outer_aliases, joins_out);
1830                for o in order_by.iter_mut() {
1831                    self.pull_up_walk(&mut o.expr, true, outer_aliases, joins_out);
1832                }
1833                if let Some(f) = filter {
1834                    self.pull_up_walk(f, true, outer_aliases, joins_out);
1835                }
1836            }
1837            Expr::Binary { lhs, rhs, .. } => {
1838                self.pull_up_walk(lhs, in_agg, outer_aliases, joins_out);
1839                self.pull_up_walk(rhs, in_agg, outer_aliases, joins_out);
1840            }
1841            Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
1842                self.pull_up_walk(expr, in_agg, outer_aliases, joins_out);
1843            }
1844            Expr::Like { expr, pattern, .. } => {
1845                self.pull_up_walk(expr, in_agg, outer_aliases, joins_out);
1846                self.pull_up_walk(pattern, in_agg, outer_aliases, joins_out);
1847            }
1848            Expr::InList { expr, list, .. } => {
1849                self.pull_up_walk(expr, in_agg, outer_aliases, joins_out);
1850                for it in list.iter_mut() {
1851                    self.pull_up_walk(it, in_agg, outer_aliases, joins_out);
1852                }
1853            }
1854            Expr::Case {
1855                operand,
1856                branches,
1857                else_branch,
1858            } => {
1859                if let Some(o) = operand {
1860                    self.pull_up_walk(o, in_agg, outer_aliases, joins_out);
1861                }
1862                for (w, t) in branches.iter_mut() {
1863                    self.pull_up_walk(w, in_agg, outer_aliases, joins_out);
1864                    self.pull_up_walk(t, in_agg, outer_aliases, joins_out);
1865                }
1866                if let Some(eb) = else_branch {
1867                    self.pull_up_walk(eb, in_agg, outer_aliases, joins_out);
1868                }
1869            }
1870            // Window functions, EXISTS / IN subqueries, and other variants
1871            // are intentionally not descended for this rewrite — the
1872            // common aggregate-arg shapes above cover the reported load and
1873            // anything missed simply keeps its existing evaluation.
1874            _ => {}
1875        }
1876    }
1877
1878    /// Decide whether a correlated scalar subquery qualifies for the
1879    /// unique-key LEFT JOIN pull-up. Returns the join to append and the
1880    /// column that replaces the subquery node, or None to leave it alone.
1881    fn try_pull_up_join(
1882        &self,
1883        inner: &SelectStatement,
1884        outer_aliases: &alloc::collections::BTreeSet<String>,
1885        alias_n: usize,
1886    ) -> Option<(FromJoin, ColumnName)> {
1887        // Inner must be a single plain-table scan with one projected
1888        // column and none of the shape-breaking clauses.
1889        if !inner.ctes.is_empty()
1890            || !inner.unions.is_empty()
1891            || inner.group_by.is_some()
1892            || inner.having.is_some()
1893            || inner.distinct
1894            || !inner.order_by.is_empty()
1895            || inner.limit.is_some()
1896            || inner.offset.is_some()
1897            || inner.items.len() != 1
1898        {
1899            return None;
1900        }
1901        let from = inner.from.as_ref()?;
1902        if !from.joins.is_empty()
1903            || from.primary.lateral_subquery.is_some()
1904            || from.primary.unnest_expr.is_some()
1905            || from.primary.generate_series_args.is_some()
1906            || from.primary.as_of_segment.is_some()
1907        {
1908            return None;
1909        }
1910        let inner_table = from.primary.name.clone();
1911        let inner_alias = from
1912            .primary
1913            .alias
1914            .clone()
1915            .unwrap_or_else(|| inner_table.clone());
1916        let is_inner = |c: &ColumnName| -> bool {
1917            c.qualifier
1918                .as_deref()
1919                .is_some_and(|q| q.eq_ignore_ascii_case(&inner_alias))
1920        };
1921        let is_outer = |c: &ColumnName| -> bool {
1922            c.qualifier
1923                .as_deref()
1924                .is_some_and(|q| outer_aliases.contains(&q.to_ascii_lowercase()))
1925        };
1926        // Projected column: a single inner-qualified column.
1927        let SelectItem::Expr { expr: out_expr, .. } = &inner.items[0] else {
1928            return None;
1929        };
1930        let Expr::Column(out_col) = out_expr else {
1931            return None;
1932        };
1933        if !is_inner(out_col) {
1934            return None;
1935        }
1936        // WHERE: exactly one `inner.key = outer.col`, rest all-inner.
1937        let w = inner.where_.as_ref()?;
1938        let mut corr: Option<(String, ColumnName)> = None;
1939        let mut rest: Vec<Expr> = Vec::new();
1940        for c in reorder::split_and_conjunctions(w) {
1941            if let Expr::Binary {
1942                lhs,
1943                op: BinOp::Eq,
1944                rhs,
1945            } = c
1946                && let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref())
1947            {
1948                let pair = if is_inner(a) && is_outer(b) {
1949                    Some((a.name.clone(), b.clone()))
1950                } else if is_inner(b) && is_outer(a) {
1951                    Some((b.name.clone(), a.clone()))
1952                } else {
1953                    None
1954                };
1955                if let Some(p) = pair {
1956                    if corr.is_some() {
1957                        return None; // more than one correlation
1958                    }
1959                    corr = Some(p);
1960                    continue;
1961                }
1962            }
1963            if !expr_is_all_inner(c, &inner_alias) {
1964                return None;
1965            }
1966            rest.push(c.clone());
1967        }
1968        let (inner_key, outer_col) = corr?;
1969        // Safety gate: the correlation key must be UNIQUE / PRIMARY KEY on
1970        // the inner table so the join can't multiply outer rows.
1971        if !self.column_is_single_unique(&inner_table, &inner_key) {
1972            return None;
1973        }
1974        // Build the LEFT JOIN against a fresh alias.
1975        let fresh = alloc::format!("__plj_{alias_n}");
1976        let key_eq = Expr::Binary {
1977            lhs: alloc::boxed::Box::new(Expr::Column(ColumnName {
1978                qualifier: Some(fresh.clone()),
1979                name: inner_key,
1980            })),
1981            op: BinOp::Eq,
1982            rhs: alloc::boxed::Box::new(Expr::Column(outer_col)),
1983        };
1984        let on = rest
1985            .into_iter()
1986            .map(|mut e| {
1987                rename_qualifier(&mut e, &inner_alias, &fresh);
1988                e
1989            })
1990            .fold(key_eq, |acc, pred| Expr::Binary {
1991                lhs: alloc::boxed::Box::new(acc),
1992                op: BinOp::And,
1993                rhs: alloc::boxed::Box::new(pred),
1994            });
1995        let join = FromJoin {
1996            kind: JoinKind::Left,
1997            table: TableRef {
1998                name: inner_table,
1999                alias: Some(fresh.clone()),
2000                as_of_segment: None,
2001                unnest_expr: None,
2002                unnest_column_aliases: Vec::new(),
2003                generate_series_args: None,
2004                lateral_subquery: None,
2005                jsonb_each_text_arg: None,
2006            },
2007            on: Some(on),
2008        };
2009        let repl = ColumnName {
2010            qualifier: Some(fresh),
2011            name: out_col.name.clone(),
2012        };
2013        Some((join, repl))
2014    }
2015
2016    /// v7.34.2 (mailrs prod NOT EXISTS hot-path) — plan-time EXISTS /
2017    /// NOT EXISTS sublink pull-up to semi/anti-join. PostgreSQL's
2018    /// `convert_EXISTS_sublink_to_join`-flavoured rewrite: a correlated
2019    /// `[NOT] EXISTS (SELECT … FROM t WHERE t.k = outer.col [AND inner])`
2020    /// in the WHERE-AND spine collapses to a real JOIN against `t`. The
2021    /// per-row dispatch (clone host expr × 25 k + splice + eval) goes
2022    /// away entirely — the executor streams one tight join loop the
2023    /// same way it would for a hand-written JOIN.
2024    ///
2025    /// Shape rules:
2026    ///   * NOT EXISTS  → LEFT JOIN t AS __exsj_N ON t.k = outer.col [AND …]
2027    ///                   AND a survivor `__exsj_N.k IS NULL` conjunct
2028    ///                   stays in WHERE. Safe regardless of uniqueness:
2029    ///                   IS-NULL only fires on the LEFT-JOIN pad row,
2030    ///                   so duplicate inner matches collapse cleanly
2031    ///                   (any match drops the outer row; only no-match
2032    ///                   outer rows survive).
2033    ///   * EXISTS      → INNER JOIN. Safe only when inner.k is single-
2034    ///                   column UNIQUE / PRIMARY KEY (otherwise INNER
2035    ///                   would multiply outer rows). Gated by
2036    ///                   `column_is_single_unique`. No survivor needed
2037    ///                   in WHERE — the join itself encodes EXISTS=true.
2038    ///
2039    /// Eligible inner: single plain-table FROM, no nested JOIN / CTE /
2040    /// UNION / GROUP / HAVING / DISTINCT / ORDER / LIMIT / OFFSET, and
2041    /// WHERE = exactly one `inner.k = outer.col` correlation plus
2042    /// optional all-inner predicates that ride into the ON clause.
2043    /// Anything else is left for the per-row resolver.
2044    ///
2045    /// Returns true when at least one conjunct was pulled up.
2046    pub(crate) fn pull_up_exists_sublinks(&self, stmt: &mut SelectStatement) -> bool {
2047        if stmt.from.is_none() {
2048            return false;
2049        }
2050        let Some(where_expr) = stmt.where_.take() else {
2051            return false;
2052        };
2053        // v7.37.4 A'' — pre-disambiguate outer unqualified column refs
2054        // whose name would collide with a future pulled-up inner
2055        // table's columns. mailrs `/api/conversations` uses bare
2056        // `thread_id != ''` in outer WHERE; once we add
2057        // `__exsj_0 LEFT JOIN snoozed_conversations` (also with a
2058        // `thread_id` column), the resolver raises "ambiguous column".
2059        // Conservative: scan EXISTS / NOT EXISTS subqueries in the
2060        // WHERE we just took out, look up each inner plain-table's
2061        // column set, and for every collision column that exists in
2062        // exactly one outer table, pre-qualify it to that owning alias.
2063        let mut collision_names: alloc::collections::BTreeSet<String> =
2064            alloc::collections::BTreeSet::new();
2065        for c in reorder::split_and_conjunctions(&where_expr) {
2066            let inner_subq: Option<&SelectStatement> = match c {
2067                Expr::Exists { subquery, .. } => Some(subquery.as_ref()),
2068                Expr::Unary {
2069                    op: UnOp::Not,
2070                    expr,
2071                } => match expr.as_ref() {
2072                    Expr::Exists { subquery, .. } => Some(subquery.as_ref()),
2073                    _ => None,
2074                },
2075                _ => None,
2076            };
2077            let Some(inner) = inner_subq else { continue };
2078            let Some(from) = &inner.from else { continue };
2079            if !from.joins.is_empty() {
2080                continue;
2081            }
2082            let Some(t) = self.active_catalog().get(&from.primary.name) else {
2083                continue;
2084            };
2085            for col in &t.schema().columns {
2086                collision_names.insert(col.name.to_ascii_lowercase());
2087            }
2088        }
2089        let mut where_expr = where_expr;
2090        if !collision_names.is_empty() {
2091            let from = stmt.from.as_ref().expect("from present");
2092            let outer_tables: Vec<(String, String)> = {
2093                let mut v = Vec::new();
2094                let collect = |v: &mut Vec<(String, String)>, t: &TableRef| {
2095                    let alias = t.alias.clone().unwrap_or_else(|| t.name.clone());
2096                    v.push((alias, t.name.clone()));
2097                };
2098                collect(&mut v, &from.primary);
2099                for j in &from.joins {
2100                    collect(&mut v, &j.table);
2101                }
2102                v
2103            };
2104            let mut owner: alloc::collections::BTreeMap<String, String> =
2105                alloc::collections::BTreeMap::new();
2106            for col_lc in &collision_names {
2107                let mut matches: Vec<String> = Vec::new();
2108                for (alias, tname) in &outer_tables {
2109                    let Some(t) = self.active_catalog().get(tname) else {
2110                        continue;
2111                    };
2112                    if t.schema()
2113                        .columns
2114                        .iter()
2115                        .any(|c| c.name.eq_ignore_ascii_case(col_lc))
2116                    {
2117                        matches.push(alias.clone());
2118                    }
2119                }
2120                if matches.len() == 1 {
2121                    owner.insert(col_lc.clone(), matches.remove(0));
2122                }
2123            }
2124            if !owner.is_empty() {
2125                disambiguate_stmt_unqualified_columns(stmt, &owner);
2126                disambiguate_expr_unqualified_columns(&mut where_expr, &owner);
2127            }
2128        }
2129        let outer_aliases: alloc::collections::BTreeSet<String> = {
2130            let from = stmt.from.as_ref().expect("from present");
2131            let mut s = alloc::collections::BTreeSet::new();
2132            let push = |s: &mut alloc::collections::BTreeSet<String>, t: &TableRef| {
2133                s.insert(
2134                    t.alias
2135                        .clone()
2136                        .unwrap_or_else(|| t.name.clone())
2137                        .to_ascii_lowercase(),
2138                );
2139            };
2140            push(&mut s, &from.primary);
2141            for j in &from.joins {
2142                push(&mut s, &j.table);
2143            }
2144            s
2145        };
2146        let conjuncts = reorder::split_and_conjunctions(&where_expr);
2147        let mut survivors: Vec<Expr> = Vec::new();
2148        let mut new_joins: Vec<FromJoin> = Vec::new();
2149        let mut rewrote_any = false;
2150        for c in conjuncts {
2151            // v7.34.3 — the parser emits `NOT EXISTS(...)` as
2152            // `Expr::Unary{Not, Exists{negated:false, …}}`, NOT as
2153            // `Exists{negated:true}`. Match both shapes so the
2154            // pull-up handles both `EXISTS` and `NOT EXISTS`.
2155            let parsed: Option<(&SelectStatement, bool)> = match c {
2156                Expr::Exists { subquery, negated } => Some((subquery.as_ref(), *negated)),
2157                Expr::Unary {
2158                    op: UnOp::Not,
2159                    expr,
2160                } => match expr.as_ref() {
2161                    Expr::Exists { subquery, negated } => Some((subquery.as_ref(), !*negated)),
2162                    _ => None,
2163                },
2164                _ => None,
2165            };
2166            if let Some((subquery, neg)) = parsed {
2167                // v7.34.2 first chose `[NOT] IN (SELECT k FROM t)` first
2168                // because the `mailrs_prod_not_exists` 250 k probe
2169                // dropped 178 ms (LEFT JOIN + IS NULL form) → 74 ms
2170                // (NOT IN form). But that win was from the OUTER ORDER
2171                // BY id DESC LIMIT N walker fast path
2172                // (`try_pk_walk_top_n`), which only the InList shape
2173                // exposes (early-stop on first N survivors). For
2174                // shapes WITHOUT an outer LIMIT (e.g. `SELECT
2175                // COUNT(*) FROM messages WHERE NOT EXISTS …`) the IN
2176                // form has to materialise the entire 12.5 k inner
2177                // value set as `Vec<Expr::Literal>` before HashSet
2178                // build — pure overhead that the LEFT ANTI JOIN
2179                // executor skips by hashing the inner table directly.
2180                // v7.37.x (docker-fair NOTEX) — branch on outer
2181                // LIMIT presence: with LIMIT, prefer InList (walker
2182                // benefit); without LIMIT, prefer LEFT ANTI JOIN
2183                // (streaming build, no Expr::Literal Vec roundtrip).
2184                let outer_has_limit = stmt.limit.is_some();
2185                let try_in_first = outer_has_limit;
2186                let mut consumed = false;
2187                if try_in_first
2188                    && let Some(rewritten) =
2189                        self.try_pull_up_exists_as_in(subquery, neg, &outer_aliases)
2190                {
2191                    survivors.push(rewritten);
2192                    consumed = true;
2193                }
2194                if !consumed
2195                    && let Some((join, residual)) = self.try_pull_up_exists_sublink(
2196                        subquery,
2197                        neg,
2198                        &outer_aliases,
2199                        new_joins.len(),
2200                    )
2201                {
2202                    new_joins.push(join);
2203                    if let Some(r) = residual {
2204                        survivors.push(r);
2205                    }
2206                    consumed = true;
2207                }
2208                if !consumed
2209                    && !try_in_first
2210                    && let Some(rewritten) =
2211                        self.try_pull_up_exists_as_in(subquery, neg, &outer_aliases)
2212                {
2213                    // Fallback when LEFT ANTI JOIN refused (e.g. inner
2214                    // shape too complex) — IN form is the next best.
2215                    survivors.push(rewritten);
2216                    consumed = true;
2217                }
2218                if consumed {
2219                    rewrote_any = true;
2220                    continue;
2221                }
2222            }
2223            survivors.push(c.clone());
2224        }
2225        if !rewrote_any {
2226            stmt.where_ = Some(where_expr);
2227            return false;
2228        }
2229        EXISTS_PULLUP_FIRE_COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
2230        if !new_joins.is_empty() {
2231            stmt.from
2232                .as_mut()
2233                .expect("from present")
2234                .joins
2235                .extend(new_joins);
2236        }
2237        stmt.where_ = survivors.into_iter().reduce(|a, b| Expr::Binary {
2238            lhs: alloc::boxed::Box::new(a),
2239            op: BinOp::And,
2240            rhs: alloc::boxed::Box::new(b),
2241        });
2242        true
2243    }
2244
2245    /// v7.34.3 — emit the EXISTS conjunct as `outer.col IN (SELECT
2246    /// inner.k FROM inner.table)` (or its negated form). Eligibility
2247    /// mirrors `try_pull_up_exists_sublink` — single plain-table FROM,
2248    /// no shape-breaking clauses, exactly one `inner.k = outer.col`
2249    /// correlation plus optional all-inner predicates — except no
2250    /// uniqueness check is needed (IN handles duplicate inner.k
2251    /// fine). For the NEGATED case we ALSO require inner.k to be
2252    /// declared NOT NULL: `outer.col NOT IN (set with NULL)` returns
2253    /// UNKNOWN for every outer row in SQL three-valued logic, which
2254    /// differs from NOT EXISTS semantics. None on ineligible →
2255    /// caller falls back to the LEFT JOIN + IS NULL injection or
2256    /// the legacy per-row resolver.
2257    fn try_pull_up_exists_as_in(
2258        &self,
2259        inner: &SelectStatement,
2260        negated: bool,
2261        outer_aliases: &alloc::collections::BTreeSet<String>,
2262    ) -> Option<Expr> {
2263        if !inner.ctes.is_empty()
2264            || !inner.unions.is_empty()
2265            || inner.group_by.is_some()
2266            || inner.having.is_some()
2267            || inner.distinct
2268            || !inner.order_by.is_empty()
2269            || inner.limit.is_some()
2270            || inner.offset.is_some()
2271        {
2272            return None;
2273        }
2274        let from = inner.from.as_ref()?;
2275        if !from.joins.is_empty()
2276            || from.primary.lateral_subquery.is_some()
2277            || from.primary.unnest_expr.is_some()
2278            || from.primary.generate_series_args.is_some()
2279            || from.primary.as_of_segment.is_some()
2280        {
2281            return None;
2282        }
2283        let inner_table = from.primary.name.clone();
2284        let inner_alias = from
2285            .primary
2286            .alias
2287            .clone()
2288            .unwrap_or_else(|| inner_table.clone());
2289        let is_inner = |c: &ColumnName| -> bool {
2290            c.qualifier
2291                .as_deref()
2292                .is_some_and(|q| q.eq_ignore_ascii_case(&inner_alias))
2293        };
2294        let is_outer = |c: &ColumnName| -> bool {
2295            c.qualifier
2296                .as_deref()
2297                .is_some_and(|q| outer_aliases.contains(&q.to_ascii_lowercase()))
2298        };
2299        let w = inner.where_.as_ref()?;
2300        let mut corr: Option<(String, ColumnName)> = None;
2301        let mut rest: Vec<Expr> = Vec::new();
2302        for c in reorder::split_and_conjunctions(w) {
2303            if let Expr::Binary {
2304                lhs,
2305                op: BinOp::Eq,
2306                rhs,
2307            } = c
2308                && let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref())
2309            {
2310                let pair = if is_inner(a) && is_outer(b) {
2311                    Some((a.name.clone(), b.clone()))
2312                } else if is_inner(b) && is_outer(a) {
2313                    Some((b.name.clone(), a.clone()))
2314                } else {
2315                    None
2316                };
2317                if let Some(p) = pair {
2318                    if corr.is_some() {
2319                        return None;
2320                    }
2321                    corr = Some(p);
2322                    continue;
2323                }
2324            }
2325            if !expr_is_all_inner(c, &inner_alias) {
2326                return None;
2327            }
2328            rest.push(c.clone());
2329        }
2330        let (inner_key, outer_col) = corr?;
2331        if negated && !self.column_is_not_null(&inner_table, &inner_key) {
2332            return None;
2333        }
2334        // Build the rewritten inner SELECT: `SELECT inner.k FROM
2335        // inner.table [WHERE rest]`. The correlation conjunct is
2336        // dropped — IN-subquery handles equality membership. All-inner
2337        // residual predicates ride into the new WHERE.
2338        let mut rewritten = inner.clone();
2339        rewritten.limit = None;
2340        rewritten.offset = None;
2341        rewritten.order_by = Vec::new();
2342        rewritten.distinct = false;
2343        rewritten.where_ = rest.into_iter().reduce(|a, b| Expr::Binary {
2344            lhs: alloc::boxed::Box::new(a),
2345            op: BinOp::And,
2346            rhs: alloc::boxed::Box::new(b),
2347        });
2348        rewritten.items = alloc::vec![SelectItem::Expr {
2349            expr: Expr::Column(ColumnName {
2350                qualifier: Some(inner_alias),
2351                name: inner_key,
2352            }),
2353            alias: None,
2354        }];
2355        Some(Expr::InSubquery {
2356            expr: alloc::boxed::Box::new(Expr::Column(outer_col)),
2357            subquery: alloc::boxed::Box::new(rewritten),
2358            negated,
2359        })
2360    }
2361
2362    fn try_pull_up_exists_sublink(
2363        &self,
2364        inner: &SelectStatement,
2365        negated: bool,
2366        outer_aliases: &alloc::collections::BTreeSet<String>,
2367        alias_n: usize,
2368    ) -> Option<(FromJoin, Option<Expr>)> {
2369        if !inner.ctes.is_empty()
2370            || !inner.unions.is_empty()
2371            || inner.group_by.is_some()
2372            || inner.having.is_some()
2373            || inner.distinct
2374            || !inner.order_by.is_empty()
2375            || inner.limit.is_some()
2376            || inner.offset.is_some()
2377        {
2378            return None;
2379        }
2380        let from = inner.from.as_ref()?;
2381        if !from.joins.is_empty()
2382            || from.primary.lateral_subquery.is_some()
2383            || from.primary.unnest_expr.is_some()
2384            || from.primary.generate_series_args.is_some()
2385            || from.primary.as_of_segment.is_some()
2386        {
2387            return None;
2388        }
2389        let inner_table = from.primary.name.clone();
2390        let inner_alias = from
2391            .primary
2392            .alias
2393            .clone()
2394            .unwrap_or_else(|| inner_table.clone());
2395        let is_inner = |c: &ColumnName| -> bool {
2396            c.qualifier
2397                .as_deref()
2398                .is_some_and(|q| q.eq_ignore_ascii_case(&inner_alias))
2399        };
2400        let is_outer = |c: &ColumnName| -> bool {
2401            c.qualifier
2402                .as_deref()
2403                .is_some_and(|q| outer_aliases.contains(&q.to_ascii_lowercase()))
2404        };
2405        let w = inner.where_.as_ref()?;
2406        // v7.37.4 A'' (mailrs prod /api/conversations 2-col anti-join) —
2407        // accept multi-column correlation. Today's single-pair restriction
2408        // forced mailrs's
2409        //   NOT EXISTS (SELECT 1 FROM sc WHERE sc.thread_id = m.thread_id
2410        //                                  AND sc.account_address = mb.user_address
2411        //                                  AND sc.snoozed_until > 0)
2412        // to fall back to the batch `try_batch_correlated_exists` path,
2413        // which builds the inner set fine but then pays a per-row host-
2414        // expression clone + AST walk + eval to splice each EXISTS node
2415        // into a Bool literal (line 194-211 above). 100k join survivors ×
2416        // ~1.5 µs per splice = ~150 ms on the mini cold bench. Pulling
2417        // multi-col is the same shape SPG / PG / MySQL / MariaDB plan a
2418        // multi-key anti-join: LEFT JOIN sc ON (sc.thread_id = m.thread_id
2419        //   AND sc.account_address = mb.user_address [AND inner preds])
2420        // + WHERE sc.<first key> IS NULL. NULL semantics: a NULL on any
2421        // join key means no match, identical to NOT EXISTS three-valued
2422        // logic (the IS NULL probe matches the pad row).
2423        let mut corr_pairs: Vec<(String, ColumnName)> = Vec::new();
2424        let mut rest: Vec<Expr> = Vec::new();
2425        for c in reorder::split_and_conjunctions(w) {
2426            if let Expr::Binary {
2427                lhs,
2428                op: BinOp::Eq,
2429                rhs,
2430            } = c
2431                && let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref())
2432            {
2433                let pair = if is_inner(a) && is_outer(b) {
2434                    Some((a.name.clone(), b.clone()))
2435                } else if is_inner(b) && is_outer(a) {
2436                    Some((b.name.clone(), a.clone()))
2437                } else {
2438                    None
2439                };
2440                if let Some(p) = pair {
2441                    corr_pairs.push(p);
2442                    continue;
2443                }
2444            }
2445            if !expr_is_all_inner(c, &inner_alias) {
2446                return None;
2447            }
2448            rest.push(c.clone());
2449        }
2450        if corr_pairs.is_empty() {
2451            return None;
2452        }
2453        // Differential knob — refuse the multi-col case under test so
2454        // the baseline path (batch resolver) runs and its result can
2455        // be compared against the pullup-on path. Single-col stays on.
2456        if corr_pairs.len() > 1
2457            && EXISTS_PULLUP_MULTICOL_DISABLE.load(core::sync::atomic::Ordering::Relaxed)
2458        {
2459            return None;
2460        }
2461        // EXISTS (semi-join) requires uniqueness on EVERY inner key so
2462        // the INNER JOIN can't multiply outer rows when more than one
2463        // inner row matches the tuple. NOT EXISTS (anti-join) uses
2464        // LEFT + IS NULL and is safe regardless of inner key uniqueness:
2465        // duplicate inner matches collapse into "matched" for the
2466        // anti-join probe.
2467        if !negated {
2468            // For multi-col EXISTS today we conservatively require each
2469            // inner column to carry a single-column UNIQUE / PRIMARY KEY
2470            // — the join cardinality guarantee is per-column. A truer
2471            // composite-unique gate could relax this; the prod hot
2472            // path (mailrs) is negated so deferring is safe.
2473            for (k, _) in &corr_pairs {
2474                if !self.column_is_single_unique(&inner_table, k) {
2475                    return None;
2476                }
2477            }
2478        }
2479        let fresh = alloc::format!("__exsj_{alias_n}");
2480        // Build the ON conjunction: every (inner_key = outer_col) pair
2481        // joined by AND, then folded with the all-inner residual.
2482        let mut on_iter = corr_pairs.iter().map(|(ik, oc)| Expr::Binary {
2483            lhs: alloc::boxed::Box::new(Expr::Column(ColumnName {
2484                qualifier: Some(fresh.clone()),
2485                name: ik.clone(),
2486            })),
2487            op: BinOp::Eq,
2488            rhs: alloc::boxed::Box::new(Expr::Column(oc.clone())),
2489        });
2490        let first_key_eq = on_iter
2491            .next()
2492            .expect("corr_pairs non-empty post `is_empty()` gate");
2493        let on = rest
2494            .into_iter()
2495            .map(|mut e| {
2496                rename_qualifier(&mut e, &inner_alias, &fresh);
2497                e
2498            })
2499            .chain(on_iter)
2500            .fold(first_key_eq, |acc, pred| Expr::Binary {
2501                lhs: alloc::boxed::Box::new(acc),
2502                op: BinOp::And,
2503                rhs: alloc::boxed::Box::new(pred),
2504            });
2505        let join = FromJoin {
2506            kind: if negated {
2507                JoinKind::Left
2508            } else {
2509                JoinKind::Inner
2510            },
2511            table: TableRef {
2512                name: inner_table,
2513                alias: Some(fresh.clone()),
2514                as_of_segment: None,
2515                unnest_expr: None,
2516                unnest_column_aliases: Vec::new(),
2517                generate_series_args: None,
2518                lateral_subquery: None,
2519                jsonb_each_text_arg: None,
2520            },
2521            on: Some(on),
2522        };
2523        let residual = if negated {
2524            // anti-join: pick the FIRST inner key as the IS NULL probe.
2525            // Any IS NULL on a joined-side column is sufficient — the
2526            // LEFT-JOIN pad row sets ALL inner columns to NULL atomically,
2527            // so a single column witnesses "no match".
2528            let probe_key = corr_pairs[0].0.clone();
2529            Some(Expr::IsNull {
2530                expr: alloc::boxed::Box::new(Expr::Column(ColumnName {
2531                    qualifier: Some(fresh),
2532                    name: probe_key,
2533                })),
2534                negated: false,
2535            })
2536        } else {
2537            None
2538        };
2539        Some((join, residual))
2540    }
2541
2542    /// v7.34.3 — true when `col` on `table` is declared NOT NULL (the
2543    /// `ColumnSchema.nullable` flag is `false`). Used to gate the
2544    /// `NOT EXISTS → NOT IN` rewrite, since SQL three-valued logic
2545    /// turns `outer.col NOT IN (set with NULL)` into UNKNOWN for every
2546    /// outer row, which would differ from the NOT EXISTS semantics.
2547    fn column_is_not_null(&self, table: &str, col: &str) -> bool {
2548        let Some(t) = self.active_catalog().get(table) else {
2549            return false;
2550        };
2551        let sch = t.schema();
2552        // Direct flag — cheap path. Covers explicit NOT NULL columns
2553        // and table-level PK constraints (ddl.rs line 1252).
2554        if sch
2555            .columns
2556            .iter()
2557            .find(|c| c.name.eq_ignore_ascii_case(col))
2558            .is_some_and(|c| !c.nullable)
2559        {
2560            return true;
2561        }
2562        // v7.34.3 — inline `PRIMARY KEY` on a column definition
2563        // (e.g. `id BIGSERIAL PRIMARY KEY`) does NOT currently flip
2564        // `ColumnSchema.nullable` to false in ddl.rs (only the
2565        // table-level `CONSTRAINT … PRIMARY KEY (col)` shape does).
2566        // PK semantically implies NOT NULL, so cross-check the
2567        // installed uniqueness constraints' `is_primary_key` flag too.
2568        let Some(pos) = sch
2569            .columns
2570            .iter()
2571            .position(|c| c.name.eq_ignore_ascii_case(col))
2572        else {
2573            return false;
2574        };
2575        sch.uniqueness_constraints
2576            .iter()
2577            .any(|u| u.is_primary_key && u.columns.as_slice() == [pos])
2578    }
2579
2580    /// True when `col` on `table` is covered by a single-column UNIQUE or
2581    /// PRIMARY KEY constraint (declared and engine-enforced), or a unique
2582    /// index — i.e. an equality on it matches at most one row.
2583    fn column_is_single_unique(&self, table: &str, col: &str) -> bool {
2584        let Some(t) = self.active_catalog().get(table) else {
2585            return false;
2586        };
2587        let sch = t.schema();
2588        let Some(pos) = sch
2589            .columns
2590            .iter()
2591            .position(|c| c.name.eq_ignore_ascii_case(col))
2592        else {
2593            return false;
2594        };
2595        if sch
2596            .uniqueness_constraints
2597            .iter()
2598            .any(|u| u.columns.as_slice() == [pos])
2599        {
2600            return true;
2601        }
2602        t.index_on(pos).is_some_and(|idx| idx.is_unique)
2603    }
2604}
2605
2606// ---- subquery free-fn helpers (lib.rs split 6) ----
2607
2608/// v7.33 — true when every column in `e` is qualified to `inner_alias`
2609/// and `e` contains no nested subquery. Used by the sublink pull-up to
2610/// confirm a non-correlation conjunct is purely inner (safe to carry into
2611/// the join ON after a qualifier rename).
2612/// v7.37.4 — refuse projection expressions that would dangle after
2613/// the LIMIT 1 pullup: aggregates / window calls / EXISTS / scalar
2614/// subqueries / outer-qualified columns (the pulled-up CTE body is
2615/// uncorrelated, so an outer reference inside the projection has no
2616/// scope to bind against). All-inner column references are fine.
2617fn proj_has_disqualifying_shape(
2618    e: &Expr,
2619    inner_alias: &str,
2620    outer_aliases: &alloc::collections::BTreeSet<String>,
2621) -> bool {
2622    match e {
2623        Expr::AggregateOrdered { .. }
2624        | Expr::WindowFunction { .. }
2625        | Expr::ScalarSubquery(_)
2626        | Expr::Exists { .. } => true,
2627        Expr::FunctionCall { name, args } => {
2628            if aggregate::is_aggregate_name(name) {
2629                return true;
2630            }
2631            args.iter()
2632                .any(|a| proj_has_disqualifying_shape(a, inner_alias, outer_aliases))
2633        }
2634        Expr::Column(c) => {
2635            // Reject outer-qualified columns inside the projection
2636            // (they'd dangle in the uncorrelated CTE body). Unqualified
2637            // columns are ambiguous in a multi-table inner — for the
2638            // phase-2 single-table gate they resolve to `inner_alias`
2639            // anyway, accept them. Qualified inner refs are OK.
2640            if let Some(q) = c.qualifier.as_deref() {
2641                outer_aliases.contains(&q.to_ascii_lowercase())
2642                    && !q.eq_ignore_ascii_case(inner_alias)
2643            } else {
2644                false
2645            }
2646        }
2647        Expr::Binary { lhs, rhs, .. } => {
2648            proj_has_disqualifying_shape(lhs, inner_alias, outer_aliases)
2649                || proj_has_disqualifying_shape(rhs, inner_alias, outer_aliases)
2650        }
2651        Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
2652            proj_has_disqualifying_shape(expr, inner_alias, outer_aliases)
2653        }
2654        Expr::Like { expr, pattern, .. } => {
2655            proj_has_disqualifying_shape(expr, inner_alias, outer_aliases)
2656                || proj_has_disqualifying_shape(pattern, inner_alias, outer_aliases)
2657        }
2658        Expr::InList { expr, list, .. } => {
2659            proj_has_disqualifying_shape(expr, inner_alias, outer_aliases)
2660                || list
2661                    .iter()
2662                    .any(|it| proj_has_disqualifying_shape(it, inner_alias, outer_aliases))
2663        }
2664        Expr::Case {
2665            operand,
2666            branches,
2667            else_branch,
2668        } => {
2669            operand
2670                .as_ref()
2671                .is_some_and(|o| proj_has_disqualifying_shape(o, inner_alias, outer_aliases))
2672                || branches.iter().any(|(w, t)| {
2673                    proj_has_disqualifying_shape(w, inner_alias, outer_aliases)
2674                        || proj_has_disqualifying_shape(t, inner_alias, outer_aliases)
2675                })
2676                || else_branch
2677                    .as_ref()
2678                    .is_some_and(|b| proj_has_disqualifying_shape(b, inner_alias, outer_aliases))
2679        }
2680        Expr::ArraySubscript { target, index } => {
2681            proj_has_disqualifying_shape(target, inner_alias, outer_aliases)
2682                || proj_has_disqualifying_shape(index, inner_alias, outer_aliases)
2683        }
2684        _ => false,
2685    }
2686}
2687
2688/// v7.37.4 A'' — walk every Expr field of a SelectStatement and
2689/// qualify any unqualified column whose name is in `owner`. Skips
2690/// nested subqueries' bodies (they own their own scope) but covers
2691/// SELECT items, WHERE, GROUP BY, HAVING, ORDER BY, and the
2692/// outer FROM clause's join ON predicates. Pulled-up join names
2693/// (`__exsj_*` / `__cl1_*` / `__plj_*`) are NOT in `owner`, so this
2694/// pass is idempotent under re-runs.
2695fn disambiguate_stmt_unqualified_columns(
2696    stmt: &mut SelectStatement,
2697    owner: &alloc::collections::BTreeMap<String, String>,
2698) {
2699    for item in &mut stmt.items {
2700        if let SelectItem::Expr { expr, .. } = item {
2701            disambiguate_expr_unqualified_columns(expr, owner);
2702        }
2703    }
2704    if let Some(from) = &mut stmt.from {
2705        for j in &mut from.joins {
2706            if let Some(on) = &mut j.on {
2707                disambiguate_expr_unqualified_columns(on, owner);
2708            }
2709        }
2710    }
2711    if let Some(g) = &mut stmt.group_by {
2712        for e in g.iter_mut() {
2713            disambiguate_expr_unqualified_columns(e, owner);
2714        }
2715    }
2716    if let Some(h) = &mut stmt.having {
2717        disambiguate_expr_unqualified_columns(h, owner);
2718    }
2719    for ob in &mut stmt.order_by {
2720        disambiguate_expr_unqualified_columns(&mut ob.expr, owner);
2721    }
2722}
2723
2724fn disambiguate_expr_unqualified_columns(
2725    e: &mut Expr,
2726    owner: &alloc::collections::BTreeMap<String, String>,
2727) {
2728    match e {
2729        Expr::Column(c) => {
2730            if c.qualifier.is_none()
2731                && let Some(alias) = owner.get(&c.name.to_ascii_lowercase())
2732            {
2733                c.qualifier = Some(alias.clone());
2734            }
2735        }
2736        Expr::Binary { lhs, rhs, .. } => {
2737            disambiguate_expr_unqualified_columns(lhs, owner);
2738            disambiguate_expr_unqualified_columns(rhs, owner);
2739        }
2740        Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
2741            disambiguate_expr_unqualified_columns(expr, owner);
2742        }
2743        Expr::FunctionCall { args, .. } => {
2744            for a in args.iter_mut() {
2745                disambiguate_expr_unqualified_columns(a, owner);
2746            }
2747        }
2748        Expr::AggregateOrdered {
2749            call,
2750            order_by,
2751            filter,
2752            ..
2753        } => {
2754            disambiguate_expr_unqualified_columns(call, owner);
2755            for ob in order_by.iter_mut() {
2756                disambiguate_expr_unqualified_columns(&mut ob.expr, owner);
2757            }
2758            if let Some(f) = filter {
2759                disambiguate_expr_unqualified_columns(f, owner);
2760            }
2761        }
2762        Expr::Like { expr, pattern, .. } => {
2763            disambiguate_expr_unqualified_columns(expr, owner);
2764            disambiguate_expr_unqualified_columns(pattern, owner);
2765        }
2766        Expr::InList { expr, list, .. } => {
2767            disambiguate_expr_unqualified_columns(expr, owner);
2768            for it in list.iter_mut() {
2769                disambiguate_expr_unqualified_columns(it, owner);
2770            }
2771        }
2772        Expr::Case {
2773            operand,
2774            branches,
2775            else_branch,
2776        } => {
2777            if let Some(o) = operand {
2778                disambiguate_expr_unqualified_columns(o, owner);
2779            }
2780            for (w, t) in branches.iter_mut() {
2781                disambiguate_expr_unqualified_columns(w, owner);
2782                disambiguate_expr_unqualified_columns(t, owner);
2783            }
2784            if let Some(eb) = else_branch {
2785                disambiguate_expr_unqualified_columns(eb, owner);
2786            }
2787        }
2788        Expr::ArraySubscript { target, index } => {
2789            disambiguate_expr_unqualified_columns(target, owner);
2790            disambiguate_expr_unqualified_columns(index, owner);
2791        }
2792        // Subquery bodies own their own scope — leave untouched.
2793        _ => {}
2794    }
2795}
2796
2797fn expr_is_all_inner(e: &Expr, inner_alias: &str) -> bool {
2798    let mut cols: Vec<ColumnName> = Vec::new();
2799    let mut subs: Vec<&SelectStatement> = Vec::new();
2800    visit_expr_columns_and_subqueries(e, &mut |c| cols.push(c.clone()), &mut |s| subs.push(s));
2801    subs.is_empty()
2802        && cols.iter().all(|c| {
2803            c.qualifier
2804                .as_deref()
2805                .is_some_and(|q| q.eq_ignore_ascii_case(inner_alias))
2806        })
2807}
2808
2809/// v7.33 — rename every column qualifier equal to `from` into `to` in
2810/// place. Used to retarget an inner subquery's predicates from its
2811/// original table alias onto the fresh LEFT JOIN alias.
2812fn rename_qualifier(e: &mut Expr, from: &str, to: &str) {
2813    match e {
2814        Expr::Column(c) => {
2815            if c.qualifier
2816                .as_deref()
2817                .is_some_and(|q| q.eq_ignore_ascii_case(from))
2818            {
2819                c.qualifier = Some(to.into());
2820            }
2821        }
2822        Expr::Binary { lhs, rhs, .. } => {
2823            rename_qualifier(lhs, from, to);
2824            rename_qualifier(rhs, from, to);
2825        }
2826        Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
2827            rename_qualifier(expr, from, to);
2828        }
2829        Expr::FunctionCall { args, .. } => {
2830            for a in args.iter_mut() {
2831                rename_qualifier(a, from, to);
2832            }
2833        }
2834        Expr::Like { expr, pattern, .. } => {
2835            rename_qualifier(expr, from, to);
2836            rename_qualifier(pattern, from, to);
2837        }
2838        Expr::InList { expr, list, .. } => {
2839            rename_qualifier(expr, from, to);
2840            for it in list.iter_mut() {
2841                rename_qualifier(it, from, to);
2842            }
2843        }
2844        Expr::Case {
2845            operand,
2846            branches,
2847            else_branch,
2848        } => {
2849            if let Some(o) = operand {
2850                rename_qualifier(o, from, to);
2851            }
2852            for (w, t) in branches.iter_mut() {
2853                rename_qualifier(w, from, to);
2854                rename_qualifier(t, from, to);
2855            }
2856            if let Some(eb) = else_branch {
2857                rename_qualifier(eb, from, to);
2858            }
2859        }
2860        _ => {}
2861    }
2862}
2863
2864/// v4.23: recognise the engine errors that indicate the inner
2865/// SELECT couldn't be evaluated in isolation because it references
2866/// an outer column — used by `subquery_replacement` to skip
2867/// materialisation and let row-eval handle it instead.
2868fn is_correlation_error(e: &EngineError) -> bool {
2869    matches!(
2870        e,
2871        EngineError::Eval(
2872            eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
2873        )
2874    )
2875}
2876
2877/// v7.32 (R30 memory) — cheap static correlation pre-check.
2878///
2879/// `subquery_replacement` distinguishes a correlated subquery from an
2880/// uncorrelated one by *optimistically executing* it and catching the
2881/// resulting `ColumnNotFound` / `UnknownQualifier`. For a join-bodied
2882/// correlated subquery that catch fires only AFTER the inner FROM is
2883/// materialised — and the deferred-join pipeline clones the whole
2884/// driving table to do it (the inbox `… JOIN messages m2 …` body
2885/// clones 960k × 10 KB ≈ 10 GB at prod scale, once per outer query,
2886/// purely to be thrown away). A correlated subquery is always handled
2887/// downstream by the per-row / post-LIMIT correlated path, so spotting
2888/// it up front lets us skip the wasted materialisation entirely.
2889///
2890/// Sound for the `true` answer: returns true only when a qualified
2891/// column at the statement's own level names a qualifier that is not
2892/// one of its own FROM aliases — exactly the reference the inner exec
2893/// would fail to resolve. Everything it can't reason about cleanly
2894/// (lateral / derived FROM entries) returns false and falls through to
2895/// the existing execute-and-catch path, so behaviour is unchanged.
2896/// v7.37.x (docker-fair SCALARSQ attack) — pre-analysed plan for the
2897/// `(SELECT COUNT(*) FROM T WHERE T.pk = outer.col)` correlated
2898/// scalar subquery shape. Computing the table + index + position
2899/// lookups once per query (instead of once per outer row) drops the
2900/// per-row work to a single column read + index probe.
2901#[derive(Debug, Clone)]
2902pub struct ScalarPkProbeFastPath {
2903    /// Position in the OUTER scan schema for the column that drives
2904    /// the equality. Per row we read `row.values[outer_pos]` directly.
2905    pub outer_pos: usize,
2906    /// Catalog-qualified name of the inner table (looked up per probe).
2907    pub inner_table_name: String,
2908    /// Column position of the inner-side PK on which we probe.
2909    pub inner_pos: usize,
2910    /// v7.37.42 (docker-fair SCALARSQ attack 1) — cached insertion-order
2911    /// index of `inner_table_name` in the active catalog at PREPARE time.
2912    /// The executor and prepare share a single engine `RwLock` read guard
2913    /// per query (see `pgwire.rs` simple-query path), so the catalog
2914    /// can't mutate mid-query — the cached index stays in sync with the
2915    /// string name. The per-row probe therefore skips the
2916    /// `BTreeMap<String, usize>` descent that `Catalog::get(&str)` would
2917    /// otherwise perform, saving ~300 ns × N outer rows.
2918    pub table_idx: usize,
2919}
2920
2921impl ScalarPkProbeFastPath {
2922    /// Per-row probe. Reads `row.values[self.outer_pos]`, looks up the
2923    /// inner table and PK index, and returns `Int(1)` on a hit or
2924    /// `Int(0)` on a miss / NULL outer key.
2925    pub fn probe(&self, row: &Row<'static>) -> Value<'static> {
2926        // The engine handle is needed to access the live catalog. The
2927        // probe is called from the run-loop with the engine in scope,
2928        // so we look up the catalog via a thread_local-cached
2929        // borrow. Simpler: defer to the engine helper that takes the
2930        // pre-analysed plan + the row. Kept here as a vtable-style
2931        // entry point so the run-loop's hot path is small.
2932        let outer_int = match row.values.get(self.outer_pos) {
2933            Some(Value::BigInt(n)) => *n,
2934            Some(Value::Int(n)) => i64::from(*n),
2935            Some(Value::SmallInt(n)) => i64::from(*n),
2936            Some(Value::Null) | None => return Value::Int(0),
2937            _ => return Value::Int(0),
2938        };
2939        SCALARSQ_PK_PROBE_PLAN_OUTER_INT.store(outer_int, core::sync::atomic::Ordering::Relaxed);
2940        SCALARSQ_PK_PROBE_PLAN_FIRED.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
2941        // The actual seek lives in `Engine::probe_with_pk_fast_path` —
2942        // we can't carry an engine borrow here without a lifetime
2943        // round-trip. Returning Int(0) as a placeholder would break
2944        // semantics; instead the run-loop calls
2945        // `engine.probe_with_pk_fast_path(&self, row)` directly so
2946        // the plan's `probe()` method is used only in tests where
2947        // the table data isn't load-bearing.
2948        Value::Int(0)
2949    }
2950}
2951
2952/// v7.37.x — per-row hit counter for the plan-cached fast path.
2953pub static SCALARSQ_PK_PROBE_PLAN_FIRED: core::sync::atomic::AtomicU64 =
2954    core::sync::atomic::AtomicU64::new(0);
2955pub static SCALARSQ_PK_PROBE_PLAN_OUTER_INT: core::sync::atomic::AtomicI64 =
2956    core::sync::atomic::AtomicI64::new(0);
2957
2958/// v7.37.x (docker-fair SCALARSQ attack) — direct PK probe for the
2959/// `(SELECT COUNT(*) FROM T WHERE T.pk = outer.col)` correlated
2960/// scalar subquery shape. Returns `Some(Int(0))` if the probe misses
2961/// or `Some(Int(1))` if it hits; `None` when the shape doesn't match
2962/// (caller falls back to per-row exec). Bypasses parse / resolve /
2963/// plan / aggregate; the SCALARSQ docker-fair bench drops from
2964/// per-row ~3 µs to per-row ~100 ns.
2965impl Engine {
2966    /// Run a pre-analysed PK probe against the live catalog. Used by
2967    /// the per-row projection fast path to avoid going through
2968    /// `eval_expr_with_correlated`.
2969    pub(crate) fn probe_with_pk_fast_path(
2970        &self,
2971        plan: &ScalarPkProbeFastPath,
2972        row: &Row<'static>,
2973    ) -> Value<'static> {
2974        let outer_int = match row.values.get(plan.outer_pos) {
2975            Some(Value::BigInt(n)) => *n,
2976            Some(Value::Int(n)) => i64::from(*n),
2977            Some(Value::SmallInt(n)) => i64::from(*n),
2978            Some(Value::Null) | None => return Value::Int(0),
2979            _ => return Value::Int(0),
2980        };
2981        // v7.37.42 attack 1 — bypass per-row `BTreeMap<String,usize>::get`
2982        // by going through the cached positional index. The prepare-time
2983        // analyser stores the index against the same catalog snapshot
2984        // the executor sees (same engine read guard), so the cached
2985        // index remains valid for the query's duration.
2986        let Some(inner_table) = self.active_catalog().tables_at(plan.table_idx) else {
2987            return Value::Int(0);
2988        };
2989        let Some(idx) = inner_table.index_on(plan.inner_pos) else {
2990            return Value::Int(0);
2991        };
2992        let Some(key) = spg_storage::IndexKey::from_value(&Value::BigInt(outer_int)) else {
2993            return Value::Int(0);
2994        };
2995        let hit = !idx.lookup_eq(&key).is_empty();
2996        SCALARSQ_PK_PROBE_FIRED.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
2997        Value::Int(i32::from(hit))
2998    }
2999
3000    /// Analyse a scalar subquery against the OUTER scan schema; return
3001    /// a `ScalarPkProbeFastPath` plan when the canonical shape is
3002    /// recognised, otherwise `None`. The outer alias and column-name
3003    /// resolution use the scan schema so the run-loop can read the
3004    /// outer value by position.
3005    pub(crate) fn analyse_scalar_count_pk_eq_probe(
3006        &self,
3007        inner: &SelectStatement,
3008        outer_schema: &[spg_storage::ColumnSchema],
3009        outer_alias: &str,
3010    ) -> Option<ScalarPkProbeFastPath> {
3011        use spg_sql::ast::{BinOp, ColumnName, SelectItem};
3012        if !inner.ctes.is_empty()
3013            || !inner.unions.is_empty()
3014            || inner.group_by.is_some()
3015            || inner.having.is_some()
3016            || inner.distinct
3017            || !inner.order_by.is_empty()
3018            || inner.limit.is_some()
3019            || inner.offset.is_some()
3020            || inner.items.len() != 1
3021        {
3022            return None;
3023        }
3024        let SelectItem::Expr { expr, .. } = &inner.items[0] else {
3025            return None;
3026        };
3027        let is_count_shape = match expr {
3028            Expr::FunctionCall { name, args } => {
3029                (name.eq_ignore_ascii_case("count_star") && args.is_empty())
3030                    || name.eq_ignore_ascii_case("count")
3031            }
3032            _ => false,
3033        };
3034        if !is_count_shape {
3035            return None;
3036        }
3037        let from = inner.from.as_ref()?;
3038        if !from.joins.is_empty()
3039            || from.primary.lateral_subquery.is_some()
3040            || from.primary.unnest_expr.is_some()
3041            || from.primary.generate_series_args.is_some()
3042            || from.primary.as_of_segment.is_some()
3043        {
3044            return None;
3045        }
3046        let inner_table_name = from.primary.name.clone();
3047        let inner_alias = from
3048            .primary
3049            .alias
3050            .as_deref()
3051            .unwrap_or(inner_table_name.as_str());
3052        let where_expr = inner.where_.as_ref()?;
3053        let Expr::Binary {
3054            lhs,
3055            op: BinOp::Eq,
3056            rhs,
3057        } = where_expr
3058        else {
3059            return None;
3060        };
3061        let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref()) else {
3062            return None;
3063        };
3064        let pick = |x: &ColumnName, y: &ColumnName| -> Option<(String, ColumnName)> {
3065            if x.qualifier
3066                .as_deref()
3067                .is_some_and(|q| q.eq_ignore_ascii_case(inner_alias))
3068            {
3069                Some((x.name.clone(), y.clone()))
3070            } else {
3071                None
3072            }
3073        };
3074        let (inner_col_name, outer_col) = pick(a, b).or_else(|| pick(b, a))?;
3075        // Outer column must be in the scan schema and qualified to
3076        // outer_alias (or unqualified).
3077        if let Some(q) = outer_col.qualifier.as_deref()
3078            && !q.eq_ignore_ascii_case(outer_alias)
3079        {
3080            return None;
3081        }
3082        let outer_pos = outer_schema
3083            .iter()
3084            .position(|c| c.name.eq_ignore_ascii_case(&outer_col.name))?;
3085        // Inner column must be a single-column PK on an integer family.
3086        // v7.37.42 attack 1 — resolve the inner table's positional index
3087        // alongside the table fetch so the per-row probe can skip the
3088        // `BTreeMap<String,usize>::get(&str)` descent.
3089        let catalog = self.active_catalog();
3090        let table_idx = catalog.tables_position_of(inner_table_name.as_str())?;
3091        let inner_table = catalog.tables_at(table_idx)?;
3092        let inner_schema_ref = inner_table.schema();
3093        let inner_pos = inner_schema_ref
3094            .columns
3095            .iter()
3096            .position(|c| c.name.eq_ignore_ascii_case(&inner_col_name))?;
3097        if !matches!(
3098            inner_schema_ref.columns[inner_pos].ty,
3099            spg_storage::DataType::BigInt
3100                | spg_storage::DataType::Int
3101                | spg_storage::DataType::SmallInt
3102        ) {
3103            return None;
3104        }
3105        if !inner_schema_ref
3106            .uniqueness_constraints
3107            .iter()
3108            .any(|u| u.is_primary_key && u.columns.as_slice() == [inner_pos])
3109        {
3110            return None;
3111        }
3112        Some(ScalarPkProbeFastPath {
3113            outer_pos,
3114            inner_table_name,
3115            inner_pos,
3116            table_idx,
3117        })
3118    }
3119
3120    pub(crate) fn try_scalar_count_pk_eq_probe(
3121        &self,
3122        inner: &SelectStatement,
3123        row: &Row<'static>,
3124        ctx: &EvalContext<'_>,
3125    ) -> Result<Option<Value<'static>>, EngineError> {
3126        use spg_sql::ast::{BinOp, ColumnName, SelectItem};
3127        if !inner.ctes.is_empty()
3128            || !inner.unions.is_empty()
3129            || inner.group_by.is_some()
3130            || inner.having.is_some()
3131            || inner.distinct
3132            || !inner.order_by.is_empty()
3133            || inner.limit.is_some()
3134            || inner.offset.is_some()
3135            || inner.items.len() != 1
3136        {
3137            return Ok(None);
3138        }
3139        let SelectItem::Expr { expr, .. } = &inner.items[0] else {
3140            return Ok(None);
3141        };
3142        let is_count_shape = match expr {
3143            Expr::FunctionCall { name, args } => {
3144                (name.eq_ignore_ascii_case("count_star") && args.is_empty())
3145                    || name.eq_ignore_ascii_case("count")
3146            }
3147            _ => false,
3148        };
3149        if !is_count_shape {
3150            return Ok(None);
3151        }
3152        let Some(from) = &inner.from else {
3153            return Ok(None);
3154        };
3155        if !from.joins.is_empty()
3156            || from.primary.lateral_subquery.is_some()
3157            || from.primary.unnest_expr.is_some()
3158            || from.primary.generate_series_args.is_some()
3159            || from.primary.as_of_segment.is_some()
3160        {
3161            return Ok(None);
3162        }
3163        let inner_table_name = from.primary.name.as_str();
3164        let inner_alias = from.primary.alias.as_deref().unwrap_or(inner_table_name);
3165        let Some(where_expr) = &inner.where_ else {
3166            return Ok(None);
3167        };
3168        let Expr::Binary {
3169            lhs,
3170            op: BinOp::Eq,
3171            rhs,
3172        } = where_expr
3173        else {
3174            return Ok(None);
3175        };
3176        let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref()) else {
3177            return Ok(None);
3178        };
3179        let pick = |x: &ColumnName, y: &ColumnName| -> Option<(String, ColumnName)> {
3180            if x.qualifier
3181                .as_deref()
3182                .is_some_and(|q| q.eq_ignore_ascii_case(inner_alias))
3183            {
3184                Some((x.name.clone(), y.clone()))
3185            } else {
3186                None
3187            }
3188        };
3189        let Some((inner_col_name, outer_col)) = pick(a, b).or_else(|| pick(b, a)) else {
3190            return Ok(None);
3191        };
3192        let catalog = self.active_catalog();
3193        let Some(inner_table) = catalog.get(inner_table_name) else {
3194            return Ok(None);
3195        };
3196        let inner_schema = inner_table.schema();
3197        let Some(inner_pos) = inner_schema
3198            .columns
3199            .iter()
3200            .position(|c| c.name.eq_ignore_ascii_case(&inner_col_name))
3201        else {
3202            return Ok(None);
3203        };
3204        if !matches!(
3205            inner_schema.columns[inner_pos].ty,
3206            spg_storage::DataType::BigInt
3207                | spg_storage::DataType::Int
3208                | spg_storage::DataType::SmallInt
3209        ) {
3210            return Ok(None);
3211        }
3212        if !inner_schema
3213            .uniqueness_constraints
3214            .iter()
3215            .any(|u| u.is_primary_key && u.columns.as_slice() == [inner_pos])
3216        {
3217            return Ok(None);
3218        }
3219        let outer_val = match eval::eval_expr(&Expr::Column(outer_col), row, ctx) {
3220            Ok(v) => v,
3221            Err(_) => return Ok(None),
3222        };
3223        let outer_int = match outer_val {
3224            Value::BigInt(n) => n,
3225            Value::Int(n) => i64::from(n),
3226            Value::SmallInt(n) => i64::from(n),
3227            Value::Null => return Ok(Some(Value::Int(0))),
3228            _ => return Ok(None),
3229        };
3230        let Some(idx) = inner_table.index_on(inner_pos) else {
3231            return Ok(None);
3232        };
3233        let Some(key) = spg_storage::IndexKey::from_value(&Value::BigInt(outer_int)) else {
3234            return Ok(None);
3235        };
3236        SCALARSQ_PK_PROBE_FIRED.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
3237        let hit = !idx.lookup_eq(&key).is_empty();
3238        Ok(Some(Value::Int(i32::from(hit))))
3239    }
3240}
3241
3242pub static SCALARSQ_PK_PROBE_FIRED: core::sync::atomic::AtomicU64 =
3243    core::sync::atomic::AtomicU64::new(0);
3244
3245/// v7.37.x (docker-fair SCALARSQ attack) — return the SQL empty-set
3246/// default for a scalar subquery's output expression. PG semantics
3247/// distinguish `COUNT(*)` (0 over an empty set) from other aggregates
3248/// (NULL). Called by the batched ScalarSubquery resolver when a
3249/// per-outer-row probe finds no matching inner partition.
3250fn scalar_subquery_empty_default(inner: &SelectStatement) -> Value<'static> {
3251    use spg_sql::ast::SelectItem;
3252    if inner.items.len() != 1 {
3253        return Value::Null;
3254    }
3255    let SelectItem::Expr { expr, .. } = &inner.items[0] else {
3256        return Value::Null;
3257    };
3258    fn is_count(e: &Expr) -> bool {
3259        match e {
3260            // COUNT(*) parses as `count_star`; COUNT(col) as `count`.
3261            // Both have BIGINT-shaped empty-set default of 0.
3262            Expr::FunctionCall { name, .. } => {
3263                name.eq_ignore_ascii_case("count") || name.eq_ignore_ascii_case("count_star")
3264            }
3265            Expr::AggregateOrdered { call, .. } => is_count(call),
3266            _ => false,
3267        }
3268    }
3269    if is_count(expr) {
3270        Value::Int(0)
3271    } else {
3272        Value::Null
3273    }
3274}
3275
3276pub(crate) fn select_is_correlated(s: &SelectStatement) -> bool {
3277    use spg_sql::ast::SelectItem;
3278    let Some(from) = &s.from else {
3279        // No FROM: correlated iff some projected column is qualified
3280        // (a qualifier with nothing to bind to is necessarily outer).
3281        let mut qualified = false;
3282        for item in &s.items {
3283            if let SelectItem::Expr { expr, .. } = item {
3284                visit_expr_columns_and_subqueries(
3285                    expr,
3286                    &mut |c| {
3287                        if c.qualifier.is_some() {
3288                            qualified = true;
3289                        }
3290                    },
3291                    &mut |_| {},
3292                );
3293            }
3294        }
3295        return qualified;
3296    };
3297    // Lateral / derived FROM entries put scope resolution beyond this
3298    // cheap check — defer to execute-and-catch.
3299    if from.primary.lateral_subquery.is_some() {
3300        return false;
3301    }
3302    let mut inner: Vec<&str> = Vec::new();
3303    if let Some(a) = &from.primary.alias {
3304        inner.push(a.as_str());
3305    }
3306    if !from.primary.name.is_empty() {
3307        inner.push(from.primary.name.as_str());
3308    }
3309    for j in &from.joins {
3310        if j.table.lateral_subquery.is_some() {
3311            return false;
3312        }
3313        if let Some(a) = &j.table.alias {
3314            inner.push(a.as_str());
3315        }
3316        if !j.table.name.is_empty() {
3317            inner.push(j.table.name.as_str());
3318        }
3319    }
3320    // Gather every expression position that evaluates in this
3321    // statement's own scope (NOT inside nested subquery bodies — the
3322    // visitor reports those via the subquery callback, which we drop).
3323    let mut exprs: Vec<&Expr> = Vec::new();
3324    for item in &s.items {
3325        if let SelectItem::Expr { expr, .. } = item {
3326            exprs.push(expr);
3327        }
3328    }
3329    if let Some(w) = &s.where_ {
3330        exprs.push(w);
3331    }
3332    for j in &from.joins {
3333        if let Some(on) = &j.on {
3334            exprs.push(on);
3335        }
3336    }
3337    if let Some(gs) = &s.group_by {
3338        for g in gs {
3339            exprs.push(g);
3340        }
3341    }
3342    if let Some(h) = &s.having {
3343        exprs.push(h);
3344    }
3345    for o in &s.order_by {
3346        exprs.push(&o.expr);
3347    }
3348    let mut correlated = false;
3349    for e in exprs {
3350        visit_expr_columns_and_subqueries(
3351            e,
3352            &mut |c| {
3353                if let Some(q) = &c.qualifier
3354                    && !inner.iter().any(|a| a.eq_ignore_ascii_case(q))
3355                {
3356                    correlated = true;
3357                }
3358            },
3359            &mut |_| {},
3360        );
3361    }
3362    correlated
3363}
3364
3365/// v7.29 (3c) — pre-order collection of SCALAR subquery nodes in a
3366/// host expression (no descent into subquery bodies). The splice
3367/// walk below uses the same order; the pair must stay in lockstep.
3368pub(crate) fn collect_scalar_subqueries<'a>(e: &'a Expr, out: &mut Vec<&'a SelectStatement>) {
3369    match e {
3370        Expr::ScalarSubquery(s) => out.push(s),
3371        Expr::Exists { .. } | Expr::InSubquery { .. } => {}
3372        Expr::Binary { lhs, rhs, .. } => {
3373            collect_scalar_subqueries(lhs, out);
3374            collect_scalar_subqueries(rhs, out);
3375        }
3376        Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
3377            collect_scalar_subqueries(expr, out);
3378        }
3379        Expr::Like { expr, pattern, .. } => {
3380            collect_scalar_subqueries(expr, out);
3381            collect_scalar_subqueries(pattern, out);
3382        }
3383        Expr::FunctionCall { args, .. } => {
3384            for a in args {
3385                collect_scalar_subqueries(a, out);
3386            }
3387        }
3388        Expr::AggregateOrdered { call, order_by, .. } => {
3389            collect_scalar_subqueries(call, out);
3390            for o in order_by {
3391                collect_scalar_subqueries(&o.expr, out);
3392            }
3393        }
3394        Expr::Case {
3395            operand,
3396            branches,
3397            else_branch,
3398        } => {
3399            if let Some(op) = operand {
3400                collect_scalar_subqueries(op, out);
3401            }
3402            for (w, t) in branches {
3403                collect_scalar_subqueries(w, out);
3404                collect_scalar_subqueries(t, out);
3405            }
3406            if let Some(eb) = else_branch {
3407                collect_scalar_subqueries(eb, out);
3408            }
3409        }
3410        Expr::ArraySubscript { target, index } => {
3411            collect_scalar_subqueries(target, out);
3412            collect_scalar_subqueries(index, out);
3413        }
3414        Expr::InList { expr, list, .. } => {
3415            collect_scalar_subqueries(expr, out);
3416            for item in list {
3417                collect_scalar_subqueries(item, out);
3418            }
3419        }
3420        _ => {}
3421    }
3422}
3423
3424/// v7.29 (3d) — empty every scalar-subquery BODY in a host
3425/// expression (node kept so the splice pre-order still matches).
3426fn hollow_scalar_subqueries(e: &mut Expr) {
3427    match e {
3428        Expr::ScalarSubquery(s) => {
3429            let hollow = SelectStatement {
3430                items: Vec::new(),
3431                ..SelectStatement::default()
3432            };
3433            **s = hollow;
3434        }
3435        Expr::Exists { .. } | Expr::InSubquery { .. } => {}
3436        Expr::Binary { lhs, rhs, .. } => {
3437            hollow_scalar_subqueries(lhs);
3438            hollow_scalar_subqueries(rhs);
3439        }
3440        Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
3441            hollow_scalar_subqueries(expr);
3442        }
3443        Expr::Like { expr, pattern, .. } => {
3444            hollow_scalar_subqueries(expr);
3445            hollow_scalar_subqueries(pattern);
3446        }
3447        Expr::FunctionCall { args, .. } => {
3448            for a in args.iter_mut() {
3449                hollow_scalar_subqueries(a);
3450            }
3451        }
3452        Expr::AggregateOrdered { call, order_by, .. } => {
3453            hollow_scalar_subqueries(call);
3454            for o in order_by.iter_mut() {
3455                hollow_scalar_subqueries(&mut o.expr);
3456            }
3457        }
3458        Expr::Case {
3459            operand,
3460            branches,
3461            else_branch,
3462        } => {
3463            if let Some(op) = operand {
3464                hollow_scalar_subqueries(op);
3465            }
3466            for (w, t) in branches.iter_mut() {
3467                hollow_scalar_subqueries(w);
3468                hollow_scalar_subqueries(t);
3469            }
3470            if let Some(eb) = else_branch {
3471                hollow_scalar_subqueries(eb);
3472            }
3473        }
3474        Expr::ArraySubscript { target, index } => {
3475            hollow_scalar_subqueries(target);
3476            hollow_scalar_subqueries(index);
3477        }
3478        Expr::InList { expr, list, .. } => {
3479            hollow_scalar_subqueries(expr);
3480            for item in list.iter_mut() {
3481                hollow_scalar_subqueries(item);
3482            }
3483        }
3484        _ => {}
3485    }
3486}
3487
3488/// v7.29 (3c) — splice the i-th scalar subquery's batched value into
3489/// the cloned tree (same pre-order as collect_scalar_subqueries).
3490/// Returns Ok(false) if a literal conversion fails (caller falls
3491/// back to the resolver path).
3492fn splice_planned_subqueries(
3493    e: &mut Expr,
3494    plan: &[Option<alloc::rc::Rc<memoize::GroupMap>>],
3495    idx: &mut usize,
3496    row: &Row<'static>,
3497    ctx: &EvalContext<'_>,
3498) -> Result<bool, EngineError> {
3499    match e {
3500        Expr::ScalarSubquery(_) => {
3501            let Some(Some(gm)) = plan.get(*idx) else {
3502                return Ok(false);
3503            };
3504            *idx += 1;
3505            // v7.37.x (docker-fair SCALARSQ attack) — empty_default is
3506            // carried on the GroupMap (PG empty-set semantics: COUNT = 0,
3507            // others = NULL). The inner here may be HOLLOWED by the
3508            // template-rewrite step, so re-introspecting it for the
3509            // aggregate kind doesn't work — the construction-time
3510            // value on the GroupMap is the source of truth.
3511            let (outer_col, map, empty_default) = gm.as_ref();
3512            let key_v = eval::eval_expr(&Expr::Column(outer_col.clone()), row, ctx)
3513                .map_err(EngineError::Eval)?;
3514            let v = if matches!(key_v, Value::Null) {
3515                Value::Null
3516            } else {
3517                map.get(&aggregate::encode_key(core::slice::from_ref(&key_v)))
3518                    .cloned()
3519                    .unwrap_or_else(|| empty_default.clone())
3520            };
3521            *e = value_to_literal_expr(v)?;
3522            Ok(true)
3523        }
3524        Expr::Exists { .. } | Expr::InSubquery { .. } => Ok(true),
3525        Expr::Binary { lhs, rhs, .. } => Ok(splice_planned_subqueries(lhs, plan, idx, row, ctx)?
3526            && splice_planned_subqueries(rhs, plan, idx, row, ctx)?),
3527        Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
3528            splice_planned_subqueries(expr, plan, idx, row, ctx)
3529        }
3530        Expr::Like { expr, pattern, .. } => {
3531            Ok(splice_planned_subqueries(expr, plan, idx, row, ctx)?
3532                && splice_planned_subqueries(pattern, plan, idx, row, ctx)?)
3533        }
3534        Expr::FunctionCall { args, .. } => {
3535            for a in args.iter_mut() {
3536                if !splice_planned_subqueries(a, plan, idx, row, ctx)? {
3537                    return Ok(false);
3538                }
3539            }
3540            Ok(true)
3541        }
3542        Expr::AggregateOrdered { call, order_by, .. } => {
3543            if !splice_planned_subqueries(call, plan, idx, row, ctx)? {
3544                return Ok(false);
3545            }
3546            for o in order_by.iter_mut() {
3547                if !splice_planned_subqueries(&mut o.expr, plan, idx, row, ctx)? {
3548                    return Ok(false);
3549                }
3550            }
3551            Ok(true)
3552        }
3553        Expr::Case {
3554            operand,
3555            branches,
3556            else_branch,
3557        } => {
3558            if let Some(op) = operand {
3559                if !splice_planned_subqueries(op, plan, idx, row, ctx)? {
3560                    return Ok(false);
3561                }
3562            }
3563            for (w, t) in branches.iter_mut() {
3564                if !splice_planned_subqueries(w, plan, idx, row, ctx)?
3565                    || !splice_planned_subqueries(t, plan, idx, row, ctx)?
3566                {
3567                    return Ok(false);
3568                }
3569            }
3570            if let Some(eb) = else_branch {
3571                if !splice_planned_subqueries(eb, plan, idx, row, ctx)? {
3572                    return Ok(false);
3573                }
3574            }
3575            Ok(true)
3576        }
3577        Expr::ArraySubscript { target, index } => {
3578            Ok(splice_planned_subqueries(target, plan, idx, row, ctx)?
3579                && splice_planned_subqueries(index, plan, idx, row, ctx)?)
3580        }
3581        Expr::InList { expr, list, .. } => {
3582            if !splice_planned_subqueries(expr, plan, idx, row, ctx)? {
3583                return Ok(false);
3584            }
3585            for item in list.iter_mut() {
3586                if !splice_planned_subqueries(item, plan, idx, row, ctx)? {
3587                    return Ok(false);
3588                }
3589            }
3590            Ok(true)
3591        }
3592        _ => Ok(true),
3593    }
3594}
3595
3596/// v7.34.2 (EXISTS-FILTER baseline) — pre-order collect for EXISTS
3597/// subqueries. Mirrors `collect_scalar_subqueries` so the per-row
3598/// splice walker can re-traverse in the same order and pick the
3599/// matching planned set by ordinal index — no string repr, no
3600/// BTreeMap probe per row. ScalarSubquery / InSubquery nodes are
3601/// skipped here (they ride their own planners).
3602pub(crate) fn collect_exists_subqueries<'a>(e: &'a Expr, out: &mut Vec<&'a SelectStatement>) {
3603    match e {
3604        Expr::Exists { subquery, .. } => out.push(subquery.as_ref()),
3605        Expr::ScalarSubquery(_) | Expr::InSubquery { .. } => {}
3606        Expr::Binary { lhs, rhs, .. } => {
3607            collect_exists_subqueries(lhs, out);
3608            collect_exists_subqueries(rhs, out);
3609        }
3610        Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
3611            collect_exists_subqueries(expr, out);
3612        }
3613        Expr::Like { expr, pattern, .. } => {
3614            collect_exists_subqueries(expr, out);
3615            collect_exists_subqueries(pattern, out);
3616        }
3617        Expr::FunctionCall { args, .. } => {
3618            for a in args {
3619                collect_exists_subqueries(a, out);
3620            }
3621        }
3622        Expr::AggregateOrdered { call, order_by, .. } => {
3623            collect_exists_subqueries(call, out);
3624            for o in order_by {
3625                collect_exists_subqueries(&o.expr, out);
3626            }
3627        }
3628        Expr::Case {
3629            operand,
3630            branches,
3631            else_branch,
3632        } => {
3633            if let Some(op) = operand {
3634                collect_exists_subqueries(op, out);
3635            }
3636            for (w, t) in branches {
3637                collect_exists_subqueries(w, out);
3638                collect_exists_subqueries(t, out);
3639            }
3640            if let Some(eb) = else_branch {
3641                collect_exists_subqueries(eb, out);
3642            }
3643        }
3644        Expr::ArraySubscript { target, index } => {
3645            collect_exists_subqueries(target, out);
3646            collect_exists_subqueries(index, out);
3647        }
3648        Expr::InList { expr, list, .. } => {
3649            collect_exists_subqueries(expr, out);
3650            for item in list {
3651                collect_exists_subqueries(item, out);
3652            }
3653        }
3654        _ => {}
3655    }
3656}
3657
3658/// v7.34.2 — per-row splice for the planned EXISTS sets. Walks the
3659/// (cloned) host expression in the SAME pre-order as
3660/// `collect_exists_subqueries`, increments `idx` past each EXISTS
3661/// node, and replaces it in place with `Bool(true/false)` derived
3662/// from the planned key-set + outer-row column values. Returns
3663/// `Ok(false)` when any encountered EXISTS lacks a planned set; the
3664/// caller falls back to the legacy per-row resolver path.
3665fn splice_planned_exists(
3666    e: &mut Expr,
3667    plan: &[Option<alloc::rc::Rc<memoize::ExistsSet>>],
3668    idx: &mut usize,
3669    row: &Row<'static>,
3670    ctx: &EvalContext<'_>,
3671) -> Result<bool, EngineError> {
3672    match e {
3673        Expr::Exists { negated, .. } => {
3674            let Some(Some(es)) = plan.get(*idx) else {
3675                return Ok(false);
3676            };
3677            *idx += 1;
3678            let (outer_cols, set) = es.as_ref();
3679            let mut key_vals: Vec<Value<'static>> = Vec::with_capacity(outer_cols.len());
3680            let mut any_null = false;
3681            for oc in outer_cols {
3682                let v = eval::eval_expr(&Expr::Column(oc.clone()), row, ctx)
3683                    .map_err(EngineError::Eval)?;
3684                if matches!(v, Value::Null) {
3685                    any_null = true;
3686                }
3687                key_vals.push(v);
3688            }
3689            let present = !any_null && set.contains(&aggregate::encode_key(&key_vals));
3690            let bit = if *negated { !present } else { present };
3691            *e = Expr::Literal(Literal::Bool(bit));
3692            Ok(true)
3693        }
3694        Expr::ScalarSubquery(_) | Expr::InSubquery { .. } => Ok(true),
3695        Expr::Binary { lhs, rhs, .. } => Ok(splice_planned_exists(lhs, plan, idx, row, ctx)?
3696            && splice_planned_exists(rhs, plan, idx, row, ctx)?),
3697        Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
3698            splice_planned_exists(expr, plan, idx, row, ctx)
3699        }
3700        Expr::Like { expr, pattern, .. } => Ok(splice_planned_exists(expr, plan, idx, row, ctx)?
3701            && splice_planned_exists(pattern, plan, idx, row, ctx)?),
3702        Expr::FunctionCall { args, .. } => {
3703            for a in args.iter_mut() {
3704                if !splice_planned_exists(a, plan, idx, row, ctx)? {
3705                    return Ok(false);
3706                }
3707            }
3708            Ok(true)
3709        }
3710        Expr::AggregateOrdered { call, order_by, .. } => {
3711            if !splice_planned_exists(call, plan, idx, row, ctx)? {
3712                return Ok(false);
3713            }
3714            for o in order_by.iter_mut() {
3715                if !splice_planned_exists(&mut o.expr, plan, idx, row, ctx)? {
3716                    return Ok(false);
3717                }
3718            }
3719            Ok(true)
3720        }
3721        Expr::Case {
3722            operand,
3723            branches,
3724            else_branch,
3725        } => {
3726            if let Some(op) = operand {
3727                if !splice_planned_exists(op, plan, idx, row, ctx)? {
3728                    return Ok(false);
3729                }
3730            }
3731            for (w, t) in branches.iter_mut() {
3732                if !splice_planned_exists(w, plan, idx, row, ctx)?
3733                    || !splice_planned_exists(t, plan, idx, row, ctx)?
3734                {
3735                    return Ok(false);
3736                }
3737            }
3738            if let Some(eb) = else_branch {
3739                if !splice_planned_exists(eb, plan, idx, row, ctx)? {
3740                    return Ok(false);
3741                }
3742            }
3743            Ok(true)
3744        }
3745        Expr::ArraySubscript { target, index } => {
3746            Ok(splice_planned_exists(target, plan, idx, row, ctx)?
3747                && splice_planned_exists(index, plan, idx, row, ctx)?)
3748        }
3749        Expr::InList { expr, list, .. } => {
3750            if !splice_planned_exists(expr, plan, idx, row, ctx)? {
3751                return Ok(false);
3752            }
3753            for item in list.iter_mut() {
3754                if !splice_planned_exists(item, plan, idx, row, ctx)? {
3755                    return Ok(false);
3756                }
3757            }
3758            Ok(true)
3759        }
3760        _ => Ok(true),
3761    }
3762}
3763
3764/// v7.30.2 (mailrs round-25) — minimum element count before an
3765/// all-literal `IN` list gets a per-query membership set. Below
3766/// this the linear scan wins on build cost.
3767const INLIST_SET_THRESHOLD: usize = 64;
3768
3769/// Cheap pre-check: is a set-eligible `IN` list reachable on the
3770/// AND spine of this expression? Anything else keeps the plain
3771/// `eval_expr` path untouched.
3772fn expr_may_use_in_set(e: &Expr) -> bool {
3773    match e {
3774        Expr::InList { list, .. } => list.len() >= INLIST_SET_THRESHOLD,
3775        Expr::Binary {
3776            lhs,
3777            op: BinOp::And,
3778            rhs,
3779        } => expr_may_use_in_set(lhs) || expr_may_use_in_set(rhs),
3780        _ => false,
3781    }
3782}
3783
3784/// Analyse an `IN` list for set eligibility: every element a literal,
3785/// all of one family (integer or string, NULLs tracked separately).
3786pub(crate) fn build_in_list_set(list: &[Expr]) -> Option<memoize::InListSetEntry> {
3787    let mut has_null = false;
3788    let mut ints: hashbrown::HashSet<i64> = hashbrown::HashSet::with_capacity(list.len());
3789    let mut texts: hashbrown::HashSet<String> = hashbrown::HashSet::with_capacity(list.len());
3790    for item in list {
3791        let Expr::Literal(lit) = item else {
3792            return None;
3793        };
3794        match lit {
3795            Literal::Null => has_null = true,
3796            Literal::Integer(i) => {
3797                ints.insert(*i);
3798            }
3799            Literal::String(s) => {
3800                texts.insert(s.clone());
3801            }
3802            _ => return None,
3803        }
3804        if !ints.is_empty() && !texts.is_empty() {
3805            return None;
3806        }
3807    }
3808    let set = if !ints.is_empty() {
3809        memoize::InListSet::Int(ints)
3810    } else if !texts.is_empty() {
3811        memoize::InListSet::Text(texts)
3812    } else {
3813        return None;
3814    };
3815    Some(memoize::InListSetEntry { set, has_null })
3816}
3817
3818/// Subquery-free eval that serves large all-literal `IN` lists from
3819/// a per-query membership set (cached in the memo by node address).
3820/// Walks only the AND spine; every other node — and every needle
3821/// whose runtime family doesn't match the set — falls through to
3822/// `eval_expr`, so coercion and error semantics stay identical.
3823fn eval_with_in_sets(
3824    e: &Expr,
3825    row: &Row<'static>,
3826    ctx: &EvalContext<'_>,
3827    m: &mut memoize::MemoizeCache,
3828) -> Result<Value<'static>, EngineError> {
3829    match e {
3830        Expr::Binary {
3831            lhs,
3832            op: BinOp::And,
3833            rhs,
3834        } => {
3835            // Mirror eval_expr: both sides evaluate (no short
3836            // circuit), then SQL three-valued AND.
3837            let l = eval_with_in_sets(lhs, row, ctx, m)?;
3838            let r = eval_with_in_sets(rhs, row, ctx, m)?;
3839            eval::and_3vl(l, r).map_err(EngineError::Eval)
3840        }
3841        Expr::InList {
3842            expr: lhs,
3843            list,
3844            negated,
3845        } if list.len() >= INLIST_SET_THRESHOLD => {
3846            let key = core::ptr::from_ref::<Expr>(e) as usize;
3847            let Some(entry) = m
3848                .in_sets
3849                .entry(key)
3850                .or_insert_with(|| build_in_list_set(list))
3851            else {
3852                return eval::eval_expr(e, row, ctx).map_err(EngineError::Eval);
3853            };
3854            let needle = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
3855            let contained = match (&needle, &entry.set) {
3856                // Non-empty list + NULL needle → NULL (negation of
3857                // NULL is still NULL).
3858                (Value::Null, _) => return Ok(Value::Null),
3859                (Value::SmallInt(n), memoize::InListSet::Int(s)) => s.contains(&i64::from(*n)),
3860                (Value::Int(n), memoize::InListSet::Int(s)) => s.contains(&i64::from(*n)),
3861                (Value::BigInt(n), memoize::InListSet::Int(s)) => s.contains(n),
3862                (Value::Text(t), memoize::InListSet::Text(s)) => s.contains(t.as_ref()),
3863                // Cross-family needle (e.g. Float vs integer list):
3864                // keep apply_binary's coercion / error behaviour.
3865                _ => return eval::eval_expr(e, row, ctx).map_err(EngineError::Eval),
3866            };
3867            let inner = if contained {
3868                Value::Bool(true)
3869            } else if entry.has_null {
3870                Value::Null
3871            } else {
3872                Value::Bool(false)
3873            };
3874            Ok(match (negated, inner) {
3875                (true, Value::Bool(b)) => Value::Bool(!b),
3876                (_, v) => v,
3877            })
3878        }
3879        _ => eval::eval_expr(e, row, ctx).map_err(EngineError::Eval),
3880    }
3881}
3882
3883fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row<'static>, ctx: &EvalContext<'_>) {
3884    // v7.24 (round-16 B) — joined outer contexts carry no single
3885    // table alias; their schemas use composite "alias.column" names
3886    // instead. Pass an unmatchable alias and let the composite
3887    // lookup in substitute_in_expr do the work (a correlated EXISTS
3888    // under a JOIN previously skipped substitution entirely and
3889    // died with "unknown table qualifier").
3890    let outer_alias = ctx.table_alias.unwrap_or("");
3891    substitute_in_select(stmt, row, ctx, outer_alias);
3892}
3893
3894fn substitute_in_select(
3895    stmt: &mut SelectStatement,
3896    row: &Row<'static>,
3897    ctx: &EvalContext<'_>,
3898    outer_alias: &str,
3899) {
3900    for item in &mut stmt.items {
3901        if let SelectItem::Expr { expr, .. } = item {
3902            substitute_in_expr(expr, row, ctx, outer_alias);
3903        }
3904    }
3905    if let Some(w) = &mut stmt.where_ {
3906        substitute_in_expr(w, row, ctx, outer_alias);
3907    }
3908    if let Some(gs) = &mut stmt.group_by {
3909        for g in gs {
3910            substitute_in_expr(g, row, ctx, outer_alias);
3911        }
3912    }
3913    if let Some(h) = &mut stmt.having {
3914        substitute_in_expr(h, row, ctx, outer_alias);
3915    }
3916    for o in &mut stmt.order_by {
3917        substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
3918    }
3919    for (_, peer) in &mut stmt.unions {
3920        substitute_in_select(peer, row, ctx, outer_alias);
3921    }
3922}
3923
3924fn substitute_in_expr(e: &mut Expr, row: &Row<'static>, ctx: &EvalContext<'_>, outer_alias: &str) {
3925    // v7.25.2 (round-19 A) — bare synthetic columns. The aggregate
3926    // rewriter replaces group-key references INSIDE subquery bodies
3927    // with `__grp_N` so a correlated subquery in a GROUP BY select
3928    // list can resolve against the synthesised group row. The names
3929    // are engine-generated, so they can't shadow user columns.
3930    if let Expr::Column(c) = e
3931        && c.qualifier.is_none()
3932        && (c.name.starts_with("__grp_") || c.name.starts_with("__agg_"))
3933        && let Some(idx) = ctx.columns.iter().position(|sc| sc.name == c.name)
3934    {
3935        let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
3936        if let Ok(lit) = value_to_literal_expr(v) {
3937            *e = lit;
3938            return;
3939        }
3940    }
3941    if let Expr::Column(c) = e
3942        && let Some(qual) = &c.qualifier
3943    {
3944        // Look up the column's index in the outer schema: plain name
3945        // when the qualifier is the outer table's alias, composite
3946        // "alias.column" for joined outer schemas (v7.24).
3947        let idx = if !outer_alias.is_empty() && qual.eq_ignore_ascii_case(outer_alias) {
3948            ctx.columns
3949                .iter()
3950                .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
3951        } else {
3952            None
3953        }
3954        .or_else(|| {
3955            let composite = alloc::format!("{qual}.{name}", name = c.name);
3956            ctx.columns
3957                .iter()
3958                .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
3959        });
3960        if let Some(idx) = idx {
3961            let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
3962            if let Ok(lit) = value_to_literal_expr(v) {
3963                *e = lit;
3964                return;
3965            }
3966        }
3967    }
3968    match e {
3969        Expr::AggregateOrdered { call, order_by, .. } => {
3970            substitute_in_expr(call, row, ctx, outer_alias);
3971            for o in order_by.iter_mut() {
3972                substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
3973            }
3974        }
3975        Expr::Binary { lhs, rhs, .. } => {
3976            substitute_in_expr(lhs, row, ctx, outer_alias);
3977            substitute_in_expr(rhs, row, ctx, outer_alias);
3978        }
3979        Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
3980            substitute_in_expr(expr, row, ctx, outer_alias);
3981        }
3982        Expr::Like { expr, pattern, .. } => {
3983            substitute_in_expr(expr, row, ctx, outer_alias);
3984            substitute_in_expr(pattern, row, ctx, outer_alias);
3985        }
3986        Expr::FunctionCall { args, .. } => {
3987            for a in args {
3988                substitute_in_expr(a, row, ctx, outer_alias);
3989            }
3990        }
3991        Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
3992        Expr::WindowFunction {
3993            args,
3994            partition_by,
3995            order_by,
3996            ..
3997        } => {
3998            for a in args {
3999                substitute_in_expr(a, row, ctx, outer_alias);
4000            }
4001            for p in partition_by {
4002                substitute_in_expr(p, row, ctx, outer_alias);
4003            }
4004            for (o, _, _) in order_by {
4005                substitute_in_expr(o, row, ctx, outer_alias);
4006            }
4007        }
4008        Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
4009        Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
4010            substitute_in_select(subquery, row, ctx, outer_alias);
4011        }
4012        Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
4013        Expr::Array(items) => {
4014            for elem in items {
4015                substitute_in_expr(elem, row, ctx, outer_alias);
4016            }
4017        }
4018        Expr::ArraySubscript { target, index } => {
4019            substitute_in_expr(target, row, ctx, outer_alias);
4020            substitute_in_expr(index, row, ctx, outer_alias);
4021        }
4022        Expr::AnyAll { expr, array, .. } => {
4023            substitute_in_expr(expr, row, ctx, outer_alias);
4024            substitute_in_expr(array, row, ctx, outer_alias);
4025        }
4026        Expr::InList { expr, list, .. } => {
4027            substitute_in_expr(expr, row, ctx, outer_alias);
4028            for item in list {
4029                substitute_in_expr(item, row, ctx, outer_alias);
4030            }
4031        }
4032        Expr::Case {
4033            operand,
4034            branches,
4035            else_branch,
4036        } => {
4037            if let Some(o) = operand {
4038                substitute_in_expr(o, row, ctx, outer_alias);
4039            }
4040            for (w, t) in branches {
4041                substitute_in_expr(w, row, ctx, outer_alias);
4042                substitute_in_expr(t, row, ctx, outer_alias);
4043            }
4044            if let Some(e) = else_branch {
4045                substitute_in_expr(e, row, ctx, outer_alias);
4046            }
4047        }
4048    }
4049}
4050
4051/// Quick scan for any subquery-bearing node in a SELECT's WHERE /
4052/// projection / `order_by` — saves cloning the AST when there are
4053/// none (the common case).
4054pub fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
4055    let mut any = false;
4056    for item in &stmt.items {
4057        if let SelectItem::Expr { expr, .. } = item {
4058            any = any || expr_has_subquery(expr);
4059        }
4060    }
4061    if let Some(w) = &stmt.where_ {
4062        any = any || expr_has_subquery(w);
4063    }
4064    if let Some(h) = &stmt.having {
4065        any = any || expr_has_subquery(h);
4066    }
4067    for o in &stmt.order_by {
4068        any = any || expr_has_subquery(&o.expr);
4069    }
4070    for (_, peer) in &stmt.unions {
4071        any = any || expr_tree_has_subquery(peer);
4072    }
4073    any
4074}
4075
4076pub(crate) fn expr_has_subquery(e: &Expr) -> bool {
4077    match e {
4078        Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
4079        Expr::AggregateOrdered { call, order_by, .. } => {
4080            expr_has_subquery(call) || order_by.iter().any(|o| expr_has_subquery(&o.expr))
4081        }
4082        Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
4083        Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
4084            expr_has_subquery(expr)
4085        }
4086        Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
4087        Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
4088        Expr::Extract { source, .. } => expr_has_subquery(source),
4089        Expr::WindowFunction {
4090            args,
4091            partition_by,
4092            order_by,
4093            ..
4094        } => {
4095            args.iter().any(expr_has_subquery)
4096                || partition_by.iter().any(expr_has_subquery)
4097                || order_by.iter().any(|(e, _, _)| expr_has_subquery(e))
4098        }
4099        Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
4100        Expr::Array(items) => items.iter().any(expr_has_subquery),
4101        Expr::ArraySubscript { target, index } => {
4102            expr_has_subquery(target) || expr_has_subquery(index)
4103        }
4104        Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
4105        Expr::InList { expr, list, .. } => {
4106            expr_has_subquery(expr) || list.iter().any(expr_has_subquery)
4107        }
4108        Expr::Case {
4109            operand,
4110            branches,
4111            else_branch,
4112        } => {
4113            operand.as_deref().is_some_and(expr_has_subquery)
4114                || branches
4115                    .iter()
4116                    .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
4117                || else_branch.as_deref().is_some_and(expr_has_subquery)
4118        }
4119    }
4120}