- **The polars two-hop `count(*)` builds ONE lazy plan instead of five eager collects**: `MATCH (a {..})-[{..}]->(b {..})-[{..}]->(c {..}) [WHERE ..] RETURN count(*)` lowers to a fast path that computes the answer as a degree product — semi-join each edge arm against its two node domains, count in/out degree per middle node, sum `in*out`. Every one of those ops was issued EAGERLY, i.e. as its own `lazy().collect()`, so each intermediate materialized in full and with every edge column still attached, and no filter or projection could be pushed across an op boundary: profiled on the 20k graph-benchmark dataset the four-semi-join chain built a **199,939-row** wide frame that the degree join immediately reduced to 120,586, and 88–96% of the query's wall time sat inside those collects. The DISTINCT-domain case — the three node domains and/or the two edge matches are not all equal, which is what a `WHERE` on the middle or end node produces — is now expressed as a single lazy plan collected once, so polars pushes the src/dst projection into the semi-joins. The algebra is unchanged (same semi-joins, same `group_by().len()`, same `(in*out).sum().fill_null(0).cast(Int64)`), so the value is identical including openCypher's count-over-no-rows `0` on an empty match. The EQUAL-domain case is deliberately NOT routed through it: that shape takes a degree-count branch whose counts are memoized on the Plottable across calls, and fusing it would trade a cross-call cache hit for a per-call replan — it is byte-identical to before, and a test asserts the fused lane is not even *called* for it. The lane DECLINES (falling through to the untouched eager code, never answering differently) for non-eager-polars frames and for an edge column already carrying a degree-counter name. Not a GPU change: like the eager code and the fused two-star lane, it collects on CPU polars for both `polars` and `polars-gpu`. **Measured** on dgx-spark, matched cross-engine graph-benchmark lane (all nine OLAP queries, same query text per engine), one perf lock per scale, position-balanced `K M S S M K` slots ×2, per-slot medians (never best-of), rows and canonical values compared on every cell: **`engine='polars'` distinct-domain two-hop `count(*)` `16.91 → 10.02 ms` at 20k (−40.7%) and `68.04 → 37.96 ms` at 100k (−44.2%)**, per-slot ranges non-overlapping at both scales (20k 15.90–18.05 vs 9.23–10.29; 100k 65.9–69.7 vs 36.5–39.3). Against same-session embedded Kuzu that turns the 20k cell from a 1.55× LOSS into a **1.09× WIN** (Kuzu 10.90 ms, ranges non-overlapping) and widens the 100k cell from a 1.25× win to **2.23×** (Kuzu 84.73 ms). The equal-domain cell is UNCHANGED by this PR at both scales (20k 2.44 → 2.30 ms, 100k 5.46 → 5.62 ms — ranges overlap = TIE, which is the point of the measurement). That cell's headline number is NOT one-shot-honest and this changelog does not claim it as a win: the degree counts it reports are memoized across calls onto the caller's `Plottable` (#1825), so a warm repeat call is fast while a one-shot query loses to embedded Kuzu; this PR neither causes nor fixes #1825. What this PR does do for that shape is make its MEMO-MISS branch — the branch a one-shot query, a rebound `Plottable`, or any future removal of the cross-call memo actually runs — build the two degree frames as ONE lazy plan (`collect_all` over a shared filtered-edge sub-plan) instead of materializing the whole filtered edge frame and grouping it twice eagerly. Same algebra, same values; the memo HIT returns before reaching it, so a warm call is untouched. **Measured** the same way (dgx-spark, perf lock, position-balanced `A B B A B A A B` slots, per-slot medians, 21 runs + 5 warmup per slot, value- and row-identical across arms): equal-domain memo-MISS **`10.94 → 8.25 ms` at 20k (−24.5%, ranges 10.24–11.10 vs 7.65–8.71, non-overlapping)** and **`48.27 → 33.13 ms` at 100k (−31.4%, 47.65–48.89 vs 32.68–33.66, non-overlapping)**; the same query issued ONE-SHOT on a fresh `Plottable` goes `12.19 → 9.18 ms` at 20k and `49.67 → 34.62 ms` at 100k. The memo-HIT cell (20k 1.84 → 2.14 ms, 100k 5.41 → 5.84 ms), the distinct-domain cell, and a `bind_only` control all come back as TIEs, which is what shows the change is confined to the miss branch. Every other cell on both engines is a TIE. The pandas arm — untouched by this change — reproduces the reference board to +0.3%…+4.1%, which is what shows the harness matches it. Value identity is the gate throughout: a differential over 972 query shapes × 6 graphs (5,832 comparisons, 4,815 with the fused lane engaged) found zero divergences from the eager code or from the pandas oracle, and pinned tests cover multiplicity (parallel edges, self-loops, duplicate node rows), empty matches, non-numeric ids, degenerate column bindings, and both declines.
0 commit comments