Problem
#1706 makes GRAPH { MATCH ... WHERE ... } safe by applying single-node/single-edge residual filters as graph masks and rejecting harder cases such as pattern predicates and multi-alias conditions. That avoids silent wrong results, but the rejection should not be the long-term ceiling.
The original GRAPH { } intent is graph-pipeline composition: keep _nodes and _edges flowing across stages without forcing row output unless the query shape truly needs it. Some rejected predicates do need joined match rows, but that can be an explicit strictness/eagerness boundary instead of a compile error.
Example currently rejected shapes:
GRAPH { MATCH (a)-[r]->(b) WHERE a.score > b.score }
GRAPH { MATCH (a)-[r]->(b) WHERE (a)-[:R]->() }
GRAPH { MATCH (a)-[r]->(b) WHERE EXISTS { (a)-[:R]->() } }
Desired behavior
Support these cases by making the boundary explicit and inspectable:
- Preserve current strict/default behavior unless we deliberately opt into an eager boundary, or choose a repo-consistent default with an explicit warning/plan marker.
- When a
GRAPH { } predicate requires joined match rows, materialize/evaluate the needed row binding set, then project the surviving rows back into graph state (_nodes, _edges) for later USE, GRAPH { ... }, and graph-preserving CALL graphistry.*.write() stages.
- Expose the boundary in a normal repo-consistent way: logical/physical plan annotation, validation issue/warning, or equivalent query-inspection surface. The important bit is that users and benchmark authors can detect when a graph pipeline crossed from pure graph filtering into row-materializing execution.
- Keep strict/benchmark integrity available, similar in spirit to existing
auto vs strict off-engine call policy: no hidden materialization when the caller asked for strictness.
Suggested implementation path
- Extend the Cypher lowering/plan metadata near
CompiledGraphResidualFilter / _compile_graph_residual_filters to classify row-shaped GRAPH predicates instead of only raising.
- Add an execution path that evaluates the joined rows and projects back to graph state. Define projection semantics explicitly for nodes and edges touched by surviving rows.
- Add an inspectable marker such as
eager_boundary, materializes_rows, or a validation warning code.
- Keep clear errors for genuinely unsupported semantics until projection rules are defined.
Acceptance criteria
GRAPH { MATCH (a)-[r]->(b) WHERE a.score > b.score } can execute in the supported eager mode and returns graph state, not row output.
- Pattern existence /
EXISTS { ... } inside GRAPH { } either works through the same boundary or produces a remaining clear unsupported error with a distinct reason.
- Query validation/inspection exposes the eager boundary so tests can assert it.
- Strict mode or equivalent can still decline the boundary for benchmark integrity.
- Docs explain the behavior in user terms: "this filter needs joined rows, so GFQL crosses an explicit eager boundary and then returns a graph."
- Tests cover pandas first; cuDF/polars behavior is either implemented or declines clearly with parity-or-NIE semantics.
Deferral / dependency
Deferred until the current GFQL correctness/perf PR stack lands, starting with #1706 and continuing through the benchmark stack. This should be stacked on top after the safety fix is merged, not folded into #1706.
Stack recommendation
Stack this after #1706. #1706 should stay the safety fix: no silent drops, clearer docs, and single-alias graph masks. This issue is the follow-up feature that changes rejected GRAPH predicates into explicit eager-boundary execution.
Problem
#1706 makes
GRAPH { MATCH ... WHERE ... }safe by applying single-node/single-edge residual filters as graph masks and rejecting harder cases such as pattern predicates and multi-alias conditions. That avoids silent wrong results, but the rejection should not be the long-term ceiling.The original
GRAPH { }intent is graph-pipeline composition: keep_nodesand_edgesflowing across stages without forcing row output unless the query shape truly needs it. Some rejected predicates do need joined match rows, but that can be an explicit strictness/eagerness boundary instead of a compile error.Example currently rejected shapes:
Desired behavior
Support these cases by making the boundary explicit and inspectable:
GRAPH { }predicate requires joined match rows, materialize/evaluate the needed row binding set, then project the surviving rows back into graph state (_nodes,_edges) for laterUSE,GRAPH { ... }, and graph-preservingCALL graphistry.*.write()stages.autovsstrictoff-engine call policy: no hidden materialization when the caller asked for strictness.Suggested implementation path
CompiledGraphResidualFilter/_compile_graph_residual_filtersto classify row-shaped GRAPH predicates instead of only raising.eager_boundary,materializes_rows, or a validation warning code.Acceptance criteria
GRAPH { MATCH (a)-[r]->(b) WHERE a.score > b.score }can execute in the supported eager mode and returns graph state, not row output.EXISTS { ... }insideGRAPH { }either works through the same boundary or produces a remaining clear unsupported error with a distinct reason.Deferral / dependency
Deferred until the current GFQL correctness/perf PR stack lands, starting with #1706 and continuing through the benchmark stack. This should be stacked on top after the safety fix is merged, not folded into #1706.
Stack recommendation
Stack this after #1706. #1706 should stay the safety fix: no silent drops, clearer docs, and single-alias graph masks. This issue is the follow-up feature that changes rejected GRAPH predicates into explicit eager-boundary execution.