Behaviour
An rrf(...) order silently ignores the order keys after it, on both engines. With four rows whose fused scores tie, order { rrf(bm25($d.body, "needle"), bm25($d.body, "needle")), $d.rank desc } limit 2 returns d1, d2 on engine v2; the keys ask for d4, d3. The same query with bm25 alone returns d4, d3, because v2 sorts a BM25 order by score, then the trailing keys, then every binding's id.
Reproduced on main (80406fee, v2) with a logic-test probe; schema Doc { slug @key, title, body @index, rank: I64 }, four rows with identical body and rank 1 to 4.
Mechanism
- The planner plans no
Sort over a fusion: optimizer.rs sort_keys returns None for Ranking::Fused ("a fusion orders its own rows").
RankFuseExec (engine/graph.rs fuse_arms, a copy of v1's body) sorts entities by fused score only (partial_cmp, NaN compared equal), truncates to the limit in entities, and never reads the trailing keys. The fused score is not a column, so rrf(...) cannot be projected (T37).
- The GQT runner already encodes the gap:
ordered_refusal refuses expect ordered for an rrf-led order because "fusion sorts by score alone, with no tie-break".
Expected
One total order for every search order: the fused score as a per-row column, then the trailing keys, then the binding ids, then limit cuts rows. The fused score becomes projectable under the same rule as nearest and bm25 (T33). Tracked as RFC 0047's total-order work (#791, rollout step 5).
Behaviour
An
rrf(...)order silently ignores the order keys after it, on both engines. With four rows whose fused scores tie,order { rrf(bm25($d.body, "needle"), bm25($d.body, "needle")), $d.rank desc } limit 2returnsd1, d2on engine v2; the keys ask ford4, d3. The same query withbm25alone returnsd4, d3, because v2 sorts a BM25 order by score, then the trailing keys, then every binding's id.Reproduced on
main(80406fee, v2) with a logic-test probe; schemaDoc { slug @key, title, body @index, rank: I64 }, four rows with identicalbodyandrank1 to 4.Mechanism
Sortover a fusion:optimizer.rssort_keysreturnsNoneforRanking::Fused("a fusion orders its own rows").RankFuseExec(engine/graph.rsfuse_arms, a copy of v1's body) sorts entities by fused score only (partial_cmp, NaN compared equal), truncates to the limit in entities, and never reads the trailing keys. The fused score is not a column, sorrf(...)cannot be projected (T37).ordered_refusalrefusesexpect orderedfor anrrf-led order because "fusion sorts by score alone, with no tie-break".Expected
One total order for every search order: the fused score as a per-row column, then the trailing keys, then the binding ids, then
limitcuts rows. The fused score becomes projectable under the same rule asnearestandbm25(T33). Tracked as RFC 0047's total-order work (#791, rollout step 5).