You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(eqp): render the SEARCH + LIST SUBQUERY for a seekable IN (SELECT) (B9a-seek)
A single non-correlated positive `col IN (SELECT …)` in the WHERE, where `col`
is seekable (a secondary-index leading column, an INTEGER PRIMARY KEY referenced
by name, or a `rowid` alias), now renders SQLite's per-candidate access plan
byte-exact:
SEARCH t USING INDEX tb (b=?)
LIST SUBQUERY 1
SCAN u USING COVERING INDEX uy
CREATE BLOOM FILTER
The executor already seeks this shape — `fold_subquery_expr` evaluates the
non-correlated subquery to a value list and `try_index_in` seeks per value; the
gap was EQP-only. Closed by:
- `in_select_folds_structurally` — the structural (non-running) half of
`eval_foldable_in_select`, so `eqp_access` can plan the seek without evaluating
the subquery (matching SQLite, which never runs it under EXPLAIN);
- an `InSelect { negated: false }` arm in `placeholder_fold_where_inner` that
folds a foldable IN to a placeholder `IN (0)` so the constraint collectors
recognise the seek;
- the LIST SUBQUERY gate now emits for the seek case too, but only when the
rendered access line *is* the IN column's own seek (`(in_col=?)`, with an IPK
referenced by name rendering as `(rowid=?)`). A competing equality/range seek
on a different column declines rather than mis-render, keeping the
"never emit a node into a non-matching plan" invariant.
The `NOT IN` / unindexed-IN bare-SCAN case (B9a) is unchanged. Residual (a
separate render node, not B9a-seek): a non-seekable / `NOT IN` whose subquery
projects an *indexed* column — SQLite renders `USING INDEX <ix> FOR IN-OPERATOR`
rather than `LIST SUBQUERY`+bloom; graphite still emits the bloom node (or, with
a competing seek, the correct access line and no IN node). Correlated / compound
bodies stay deferred.
Verified vs sqlite3 3.50.4 (EQP + row parity: rowid, secondary index, empty
list). `seek_scalar_subquery.rs`'s stale "IN (SELECT) stays SCAN" expectation is
moved to `eqp_in_subquery.rs` as a now-rendered case.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0 commit comments