@@ -1204,10 +1204,19 @@ tie/representative order), so they are perf/EQP-fidelity work, not correctness:
12041204 over a ` LIMIT ` body still declines — SQLite full-sorts the materialized ` LIMIT ` rows,
12051205 whereas pushing the ORDER BY down would render a partial ` LAST TERM ` index walk when
12061206 the leading prefix is indexed. Boundary rules in the ` eqp-derived-coroutine ` memory.
1207- - ** B9d — ` SEARCH ` + ` GROUP BY ` /` DISTINCT ` temp-b-tree node.** graphite emits the
1208- grouping b-tree only over a bare ` SCAN ` ; under a ` WHERE ` seek it omits the node.
1209- Entangled with B9h (SQLite often picks a * different* index whose walk serves the
1210- grouping), so it should land together with the index-choice work.
1207+ - ** B9d — ` SEARCH ` + ` GROUP BY ` /` DISTINCT ` temp-b-tree node. ◐ Unambiguous subset
1208+ done.** The grouping b-tree now also materializes over a ** rowid * range* seek**
1209+ (` WHERE a>? GROUP BY c ` → `SEARCH t USING INTEGER PRIMARY KEY (rowid>?)#USE TEMP
1210+ B-TREE FOR GROUP BY` , and the ` DISTINCT` analogue) — the rowid is the table's own
1211+ clustered key, so there is no secondary-index * choice* and thus no cost-model
1212+ divergence; the seek returns rows in rowid order, never the group/distinct-key
1213+ order, so the b-tree is always needed. Gated in ` eqp_select ` on a
1214+ ` SEARCH … INTEGER PRIMARY KEY (rowid>?/<?) ` access line (a rowid * equality* seek is a
1215+ single row → excluded), reusing ` group_distinct_btree ` 's existing "a secondary index
1216+ leads the first key column → decline" guard. * Still open (folded into B9h):* the
1217+ same node under a ** secondary-index** seek (` WHERE b=? GROUP BY c ` ), where SQLite may
1218+ pick a * different* composite index ` (b,c) ` whose walk serves the grouping — a
1219+ cost-model index-choice decision.
12111220- ** B9e — ` col = (scalar subquery) ` seek. ✅ Done (SELECT).** ` WHERE b = (SELECT …) `
12121221 (and ` > ` /` < ` /etc.) against a non-correlated scalar subquery now seeks — the executor
12131222 folds the subquery to its value before the seek (` scan_source ` single-table fast path),
@@ -1245,9 +1254,13 @@ tie/representative order), so they are perf/EQP-fidelity work, not correctness:
12451254 longest-equality-prefix only. It also decides * whether* a no-WHERE query uses a
12461255 covering scan at all (SQLite: narrow index beats a wide-row table scan, plain scan
12471256 beats it on a 2-column table) — so the covering-scan row-order parity (formerly B9i)
1248- rides here too. This changes the chosen access path, so it risks regressing the EQP
1249- corpus and must be rolled out shape-by-shape with the full differential suite — the
1250- single-table analogue of B1b.
1257+ rides here too, as does the ** secondary-index** ` SEARCH ` + ` GROUP BY ` /` DISTINCT `
1258+ b-tree left open by B9d. This changes the chosen access path, so it risks regressing
1259+ the EQP corpus and must be rolled out shape-by-shape with the full differential suite
1260+ — the single-table analogue of B1b. ** Confirmed deferred by design (2026-07-04):** the
1261+ pinned ` sqlite3 3.50.4 ` oracle has no stat4, so its choices depend on row-width /
1262+ index-width / index-count heuristics graphite can't reproduce without diverging the
1263+ EQP corpus — same class as B1b/B4. Needs a stat4-enabled oracle to become tractable.
12511264- ** B9i — covering-scan no-` ORDER BY ` row order → subsumed by B9h (investigated
12521265 2026-07-04, nothing to fix in isolation).** The original premise was wrong: graphite's
12531266 covering read is * already* in index-walk order, and whenever graphite and SQLite pick
@@ -1261,12 +1274,22 @@ tie/representative order), so they are perf/EQP-fidelity work, not correctness:
12611274 with two, so the plan ** and** the emitted order differ. Reproducing it is pure cost
12621275 modelling (row width, index width, number of indexes) — the same B9h/B4 problem, so
12631276 the row-order parity rides on B9h, not a separate execution-order change.
1264- - ** B9j — collation-aware index * selection* for a non-default-collation index.**
1265- ` collect_eq_constraints ` /` collect_range_constraints ` now compare an explicit
1266- ` COLLATE ` to the * column's* collation (fixes the common bug); a `CREATE INDEX …
1267- (b COLLATE NOCASE)` on a BINARY column plus a matching ` COLLATE NOCASE` predicate
1268- could seek that index, but graphite scans. Fully correct needs per-candidate-index
1269- collation comparison threaded through every seek site.
1277+ - ** B9j — collation-aware index * selection* for a non-default-collation index.
1278+ Deferred (entangled, rows already correct).** ` collect_eq_constraints ` /
1279+ ` collect_range_constraints ` compare an explicit ` COLLATE ` to the * column's*
1280+ collation. When an index carries a * non-default* collation (`CREATE INDEX ib ON
1281+ t(b COLLATE NOCASE)` on a BINARY column), graphite is wrong in ** both** directions vs
1282+ sqlite: ` b='x' COLLATE NOCASE ` should seek ` ib ` (graphite scans), and ` b='x' ` (BINARY
1283+ comparison) should * not* seek the NOCASE ` ib ` (graphite over-seeks it — rows still
1284+ correct via the WHERE re-apply, only EQP/perf). The correct model — an index serves a
1285+ comparison iff its per-column collation equals the comparison's * effective* collation
1286+ (explicit ` COLLATE ` , else the column's) — must be threaded into the index * selection*
1287+ at every one of the ~ 9 ` collect_eq_constraints ` call sites (the seek fast paths,
1288+ ` eqp_access ` , and ` seek_order_prefix ` 's ORDER-BY credit) in lockstep. The current
1289+ column-collation gate in the collector is itself an earlier ORDER-BY-ordering
1290+ correctness fix, so moving the check risks the extensive collation/seek/order suite
1291+ for a niche pattern with rows already correct. Deferred; a careful cross-cutting
1292+ refactor, not a quick slice.
12701293
12711294** Blocked / deferred by design:**
12721295- ** B1b — cost-based join reordering.** graphite's per-cursor seek/bloom-filter
0 commit comments