Conversation
…s compare codes A label or edge-type predicate is a scalar equality on a column with a handful of distinct values. gfql_index_all now codes those columns once at build time, and the array bindings path answers the predicate by comparing the candidates' codes instead of filtering a frame. Nulls get a reserved code that is never handed out for a queried value, so they match nothing, exactly as the canonical filter's three-valued logic already decides; a value the column never holds matches nothing for the same reason. Boolean and integer values never share a code, because True == 1 in Python and the canonical filter does not conflate them. Polars only; every other engine, dtype, null-free requirement and cardinality above the cap declines to the canonical filter, which is the same answer. The property-index engagement probe now watches both filter seams. It asserted that the seed was gathered from the index rather than scanned, and the work moving to the positional filter had made it blind rather than false. 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 09:40
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
A label or edge-type predicate is a scalar equality on a column with a handful of distinct values. On the seeded bindings path each one still cost a frame filter: select the predicate column, gather the candidate rows, filter, read the survivors back. On LDBC SNB IC8
recent-repliesthat is seven such filters, about 1.7 ms of a 6.0 ms query.Measured in isolation, the same predicate answered from a coded column costs 0.011 ms against 0.179 for a node label, and 0.004 against 0.145 for an edge type.
Change
gfql_index_allnow builds aCategoryIndexfor each eligible column: a per-row integer code plus the value-to-code map. The array bindings path answers a predicate by comparing the candidates' codes, which is one array gather.Three decisions carry the correctness:
True == 1in Python; the canonical filter does not conflate them, so the code lookup matches on type as well as value.Everything else declines to the canonical filter, which is the same answer: other engines, float and other dtypes, and cardinality above 64.
Index build is a declared setup step like the adjacency build, not lazy per-query work. On SF0.1 it takes
gfql_index_allfrom 101.5 ms to 144.7 ms.Measured
LDBC SNB SF0.1, H684 index lane recipe, local box, interleaved against the base branch over three rounds:
About -23% and -20%. Point queries are unchanged.
DGX A/B on the benchmark recipe
Three runs per arm against merged master 65c359b, canonical rows identical in every cell.
This measures the whole stack (#2084 + #2086 + this), since each builds on the last.
Every other cell is flat or inside its own noise. SF1 pandas new-topics reads +10.3%, which is
the cell whose identical-code A/A control spans 472.8 to 567.5 ms — see #2084 for that
investigation; its GFQL surface reaches none of this code.
Against the competitor arms on eligible cells, the stack leads 7 and loses 3:
message-repliesnow beats Kuzu by about twelve times, and
recent-replieshas gone from a 5.4x loss againstNeo4j to 1.32x. The two
seed-lookupcells andrecent-repliesremain losses, budgeted in theplan's remaining-gap document.
Tests
graphistry/tests/compute/gfql/index/test_category_index.pycovers code round-trips across Boolean, String and integer columns with and without nulls; declines for float, high cardinality, absent columns and non-polars engines; staleness against both a cloned frame and a reshaped one; and seven end-to-end predicate shapes checked against the canonical filter, including a value the column never holds and the boolean-versus-integer conflation guard.Index suite 1127 passed. Broad suite over
tests/compute/gfql, chain specializations, chain and hops: 16150 passed, 1 failed — the cuDF zero-hop test that fails identically on master with this box's cuDF version.One existing test needed a fix rather than a waiver.
test_node_property_index_seeds_without_scanningpins that a seeded query gathers its seed from the property index instead of scanning, by recording the widths passed to the frame filter. With the predicate answered positionally that probe recorded nothing, so it was blind rather than false. It now watches both filter seams; the assertion is unchanged.Stacked on #2086, which is stacked on #2084. Review those first.
🤖 Generated with Claude Code
https://claude.ai/code/session_012Me1E7ZdDuGqJGu3mMEzhp