Conversation
Array-side exact join estimate over null-free integer keys, predicate-first node gather before the wide row gather, and skip the endpoint semi-join when the node filter dropped no ids. Same rows, same order, same trace values. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Me1E7ZdDuGqJGu3mMEzhp
Replaces the per-hop join+sort plan with searchsorted ranges over step rows pre-sorted by (key, tiebreaks) and two row gathers; identical rows, order, schema. Declines to the frame plan on lazy/null/non-integer inputs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Me1E7ZdDuGqJGu3mMEzhp
rows() gains attach_prop_columns; a bare rows() immediately followed by select is rewritten to request just the alias.column names the select projects, on the pandas, cuDF, and polars bindings builders alike. Anything the select cannot be bounded to keeps attach-all. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Me1E7ZdDuGqJGu3mMEzhp
…olars before the pyspark probe
The temporal-constructor scan first checks for a literal '(' so plain text
columns skip the anchored regex; resolve_engine recognizes polars frames
before attempting the (usually absent) pyspark import. Registers the new
polars-dependent test files in the polars CI lane.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Me1E7ZdDuGqJGu3mMEzhp
…rk (#2084) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Me1E7ZdDuGqJGu3mMEzhp
Types the new array-side helpers with ArrayLike/ArrayNamespace instead of Any, takes the select pushdown's column list as Sequence[str], and folds _frame_with_positions onto the general _with_positions helper. Remaining casts carry hygiene-ok reasons. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Me1E7ZdDuGqJGu3mMEzhp
Satisfies the comment-encoding guard: the lexsort key order, the identity semi-join condition, and the temporal-text prefilter each state their constraint in one line, with no performance vocabulary. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Me1E7ZdDuGqJGu3mMEzhp
lmeyerov
marked this pull request as ready for review
September 15, 2026 06:58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
LDBC SNB IC8
recent-replies— a 3-hop seeded chain (2.6k → 2.1k → 464 frontier) that projects six columns, orders and takes 20 — ran 29.3 ms on the measured Polars lane against Neo4j 5.4 and Memgraph 6.3. Profiling on merged master showed the cost is not the traversal: each hop runs about thirteen Polars operations over ~2k-row frames, and each one pays fixed plan and thread fan-out cost. The same query at 4 Polars threads ran 12.9 ms against 22.2 at 20 threads on identical data.Changes
All four are result-preserving: same rows, same order, same dtypes, same index-trace values.
estimate_inner_join_rowscomputes its exact sum-of-products over null-free integer keys withunique/searchsortedinstead of a group-by-and-join plan. Nulls and non-integer keys keep the frame path, because engines disagree on null-key matching.searchsortedranges over step rows pre-sorted by (key, tiebreaks) plus two row gathers.rows()acceptsattach_prop_columns, and a barerows()immediately followed byselectis rewritten to attach only thealias.columnnames the projection reads — the same rewrite in the pandas/cuDF and Polars twins, and in both bindings finishers. Any item that is not a literal, a bare alias id, an edge-alias column, or an existing node column keeps attach-all.(before the anchored regex, andresolve_enginerecognizes Polars frames before probing for pyspark.Measured
LDBC SNB SF0.1, the H684 index lane recipe (native Polars frames,
gfql_index_all, auto node-property indexes, 8 warmups, 31 samples, full record materialization). Local box, 24 threads.cuDF on a local RTX: recent-replies 372 → 174, message-replies 224 → 109. Point queries are unchanged: interleaved A/B of
message-creatorover 201 samples gives master 0.590 / 0.683 / 0.564 against 0.611 / 0.578 / 0.579 ms.Thread sensitivity drops with the array-side work: recent-replies is 9.85 ms at 24 threads and 8.14 at 4, where master was 22.2 and 12.9.
DGX A/B on the benchmark recipe
Three runs per arm on the same H684 recipe against merged master 65c359b, canonical rows identical in every cell.
Every other cell lands within about two percent, except two that deserve naming.
SF1 polars seed-lookup reads +13.5%, but the per-run values overlap completely (master 0.971 / 1.325 / 1.181 against 1.362 / 1.167 / 1.341), so that cell is noise at three runs.
SF1 pandas new-topics read +14.3%, and an interleaved re-run on an idle box reproduced +19.4%, so it was investigated rather than waved off. It is not a code effect. That query's GFQL surface is a one-hop undirected traversal with
rows(source=...), which reaches none of the changed code: the projection rewrite requiressourceto be absent, and instrumenting the bindings module's own references shows zero calls to the three changed helpers. Bisecting to the first commit of this branch still showed +13.5% even though that commit provably makes no calls on the path. The measurement record explains it: the same cell on this harness reads 574.0 / 570.6 / 528.8 on master f7a7253 and 567.1 / 522.8 / 568.8 on master 92ad8c0, both of which predate this work entirely, and an A/A control settles it: running the same interleaved driver with BOTH arms at the current master, one from each of two byte-identical checkouts, gives 567.49 and 476.62 on one checkout against 472.81 and 517.53 on the other — four runs of identical code spanning 472.8 to 567.5, with the first interleaved pair 16.7% apart. The cell is bimodal across roughly 474 to 598 whatever the code is, and this branch drew from the high end.The wider lesson is written up in the benchmark evidence: a three-run median of a bulk cell whose own A/A spread approaches the acceptance gate cannot support a product-vs-product claim, so such cells need an A/A control or many more repetitions before a delta is quoted.
Tests
tests/compute/gfql/index/test_indexed_bindings_hop_overhead.py— the array estimate against the frame estimate across pandas, Polars and cuDF including null, float, nullable-integer and lazy deferrals; predicate-first gather against gather-then-filter over drops, unsorted positions, an absent column that must raise identically, and an empty gather; the covers-ids check; end-to-end indexed against canonical with and without endpoint drops, asserting the semi-join is skipped only when it is the identity; and a twelve-seed fuzz of the array expand join against the frame plan.tests/compute/gfql/test_rows_select_projection_pushdown.py— the pushdown plan itself, then every case run with and without the pushdown on pandas, Polars and cuDF including the decline cases, a spy asserting the narrowed request reaches the builder, and parameter serialization and validation.tests/compute/gfql, chain specializations, chain and hops: 16067 passed, 1162 skipped, 102 xfailed.🤖 Generated with Claude Code
https://claude.ai/code/session_012Me1E7ZdDuGqJGu3mMEzhp