fix(gfql): apply GRAPH residual predicates as masks#1706
Merged
Conversation
Node residual GRAPH masks were applied as global node masks before the graph-state chain, which changes alias-specific MATCH semantics for multi-alias patterns. Keep single-node node residual masks and edge residual masks, but reject multi-alias node residual masks loudly.
lmeyerov
commented
Jul 8, 2026
lmeyerov
commented
Jul 8, 2026
5433dbf to
40a3770
Compare
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.
What
Fixes
GRAPH { MATCH ... WHERE ... }residual predicate handling for the safe graph-mask cases instead of silently dropping the predicate or merely raising.This PR compiles single-alias residual predicates into explicit graph residual-mask metadata, then applies those masks to the target node or edge frame before the graph-state
Chainruns. That covers residual forms such as OR/NULL expressions andsearchAny(...)for a single node or edge alias.Pattern-predicate and multi-alias residuals still fail loudly because they need first-class projection semantics rather than a local entity mask. Polars also declines these GRAPH residual masks for now instead of silently bridging or mis-evaluating.
Why
Issue #1662 exposed a silent wrong-results bug: GRAPH constructors could accept row residual predicates from Cypher lowering, but
Chain(lowered.query, where=lowered.where)had nowhere to apply them.The root fix is to preserve the residual and apply it at graph state construction time when it is semantically a node or edge mask.
Handoff / backport notes
Use the net PR diff as the source of truth; the first commit (
b1903892, fail-fast) was intentionally superseded by the later mask implementation. If cherry-picking into a branch that has moved ahead, prefer either cherry-picking the full stack or reapplying the net diff for these files:graphistry/compute/gfql/cypher/lowering.py: addsCompiledGraphResidualFilter, records it on compiled query/binding structs, and compiles only safe single-alias node/edge residuals.graphistry/compute/gfql_unified.py: applies residual masks before_chain_dispatch; pandas/cuDF path is implemented, Polars raises a clear validation error.graphistry/tests/compute/gfql/cypher/test_lowering.py: semantic contract for node residuals, alias/property-column collision regressions, cuDF smoke, Polars honest decline,searchAny, edge residuals, namedGRAPH ... USE, and pattern-predicate rejection.docs/source/gfql/cypher.rst: user-facing supported/unsupported behavior.bin/ci_cypher_surface_guard_baseline.json: intentional internal compiled-struct surface growth.Backport gotchas:
graph_residual_filterson both standaloneCompiledCypherGraphQueryand namedCompiledGraphBinding; otherwiseGRAPH g = GRAPH { ... } USE g ...regresses.a,r, etc.) breaks alias-property residuals such asa.score/r.weight.Surface change
The compiled Cypher structs gain one internal metadata field for GRAPH residual masks. The Cypher surface guard baseline was regenerated intentionally for:
CompiledCypherQuery: 7 -> 8 fieldsCompiledGraphBinding: 6 -> 7 fieldsCompiledCypherGraphQuery: 6 -> 7 fieldsUser impact
GRAPH { }now filter the returned subgraph on pandas/cuDF.searchAny(...)works in the supported single-alias GRAPH mask form.Validation
python bin/ci_cypher_surface_guard.py-> passedpython -m pytest -q graphistry/tests/compute/gfql/cypher/test_lowering.py -k 'graph_constructor or graph_binding_applies_residual_mask_before_use'-> 17 passedpython -m pytest -q graphistry/tests/compute/gfql/test_engine_polars_conformance_matrix.py -k 'prune_isolated_graph_shape'-> 1 passedpython -m pytest -q graphistry/tests/compute/gfql/test_viz_pipeline_conformance.py -k 'graph_state_prune_pipeline'-> 1 passedpython -m ruff check graphistry/compute/gfql/cypher/lowering.py graphistry/compute/gfql_unified.py graphistry/tests/compute/gfql/cypher/test_lowering.py-> passedRefs #1662