Skip to content

feat(cypher): add connected multi-alias row bindings - #1008

Merged
lmeyerov merged 3 commits into
masterfrom
feat/cypher-multi-alias-row-bindings
Apr 1, 2026
Merged

lmeyerov merged 3 commits into
masterfrom
feat/cypher-multi-alias-row-bindings

Conversation

@lmeyerov

@lmeyerov lmeyerov commented Apr 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add a connected-path rows(binding_ops=...) row-binding path for direct Cypher scalar multi-alias projection
  • extend the same path to bounded WITH ... MATCH reentry so connected suffix projections can bind whole-row and carried-scalar aliases correctly
  • lock the new admitted shapes with positive, negative, empty-result, and cudf regressions

Scope

Closes #981
Closes #1006

This PR intentionally targets the connected single-path / connected multi-pattern projection lane. It does not attempt the broader row-carrier redesign from #989 or the fuller bindings_table architecture from #880.

Companion tck-gfql PR: graphistry/tck-gfql#17

Validation

  • ruff check graphistry/compute/ast.py graphistry/compute/chain.py graphistry/compute/gfql/call/validation.py graphistry/compute/gfql/cypher/lowering.py graphistry/compute/gfql/row/pipeline.py graphistry/tests/compute/gfql/cypher/test_lowering.py
  • python -m py_compile graphistry/compute/ast.py graphistry/compute/chain.py graphistry/compute/gfql/call/validation.py graphistry/compute/gfql/cypher/lowering.py graphistry/compute/gfql/row/pipeline.py graphistry/tests/compute/gfql/cypher/test_lowering.py
  • ./bin/typecheck.sh
  • PYTHONPATH=. pytest -q graphistry/tests/compute/gfql/cypher/test_lowering.py -k 'multi_alias_return_empty_match or plain_connected_multi_pattern_scalar_projection or plain_multi_alias_edge_scalar_projection or whole_row_into_connected_multi_pattern_shape or carried_scalar_into_connected_multi_pattern_shape or recent_message_reentry_multihop_scalar_projection or branching_multihop_row_bindings or multiple_trailing_match_clauses'
  • PYTHONPATH=. pytest -q graphistry/tests/compute/gfql/cypher/test_lowering.py -k 'reentry or multi_pattern or multi_alias or bindings'
  • PYTHONPATH=. pytest -q graphistry/tests/compute/test_gfql.py -k 'reentry or cypher'
  • PYTHONPATH=. pytest -q graphistry/tests/compute/gfql/cypher/test_lowering.py
  • DGX nvcr.io/nvidia/rapidsai/base:26.02-cuda13-py3.13: 6 passed in 4.09s

Notes

  • the benchmark-like interactive-short-2 shape now progresses through the old #1006 blocker because connected multi-alias bindings are materialized instead of collapsed through endpoint shortcuts
  • branching variable-length segments remain explicitly failfast
  • connected multi-pattern relationship alias projection now failfasts at compile time instead of falling through to a late runtime token error

@lmeyerov

lmeyerov commented Apr 1, 2026

Copy link
Copy Markdown
Contributor Author

Extra hardening is now pushed on 3d16ab1b9.

What changed:

  • added connected fanout positives for direct projection and carried-scalar reentry
  • added cudf coverage for the connected carried-scalar fanout shape
  • expanded unsupported multihop boundary coverage (branching, varlen rel alias, undirected varlen, non-terminating cycle)
  • fixed one adjacent boundary bug by turning connected multi-pattern relationship alias projection (t.type) from a late runtime token error into compile-time failfast

Validation:

  • focused slice: 6 passed, 1 skipped
  • broader lowering slice: 50 passed, 7 skipped
  • full lowering file: 481 passed, 48 skipped
  • test_gfql reentry/cypher slice: 24 passed, 3 skipped
  • ./bin/typecheck.sh: pass
  • DGX RAPIDS 26.02 cuda13 targeted slice: 6 passed in 4.09s
  • sibling contract sanity against graphistry/tck-gfql#17: mismatch_count 0

@lmeyerov
lmeyerov merged commit 74a5719 into master Apr 1, 2026
66 checks passed
lmeyerov added a commit that referenced this pull request Apr 1, 2026
Superseded by _serialize_binding_ops + orient_edges path from #1008.
The binding_ops code path correctly handles undirected edges by
doubling them (fwd + rev) in EdgeSemantics.orient_edges.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
lmeyerov added a commit that referenced this pull request Apr 1, 2026
Superseded by _serialize_binding_ops + orient_edges path from #1008.
The binding_ops code path correctly handles undirected edges by
doubling them (fwd + rev) in EdgeSemantics.orient_edges.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
lmeyerov added a commit that referenced this pull request Apr 1, 2026
…stence parser (#994, #997, #998) (#1009)

* fix(cypher): handle undirected edges in bindings table (#994)

For undirected MATCH patterns, the seed node can be on either side of
the stored edge. Detect which side is the seed per-row using the src
alias boolean mask, then compute peer_id for the undirected_peer alias.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix(cudf): use column check instead of .get() for cudf compat

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* test(cypher): add undirected incoming/outgoing/bidirectional tests (#994)

3 new tests for the undirected MATCH bindings table fix:
- incoming edge returns peer not seed
- outgoing edge still correct (regression)
- bidirectional edges both return peer

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* feat(cypher): parse shortestPath + pattern existence with clear fail-fast (#997, #998)

- shortestPath((a)-[:R*]-(b)) and allShortestPaths() now parse and
  raise GFQLValidationError("not yet supported") instead of
  GFQLSyntaxError("Invalid Cypher query syntax")
- not((a)-[:R]-(b)), exists { ... }, not exists { ... } pattern
  existence expressions now detected pre-parse and raise clear
  GFQLValidationError instead of generic syntax error

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* test+docs(cypher): add tests for #997/#998 fail-fast, update changelog

6 new tests: 3 for shortestPath/allShortestPaths rejection,
3 for pattern existence expression rejection.
Changelog updated with #994 fix + #997/#998 parser improvements.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* chore: retrigger CI after tck-gfql main fix

* chore: retrigger CI (tck-gfql match5-21..24 removed from xfail map)

* test(cypher): amplify #994/#998 edge case coverage

Add 4 tests: undirected with multiple edges to same nodes,
undirected star graph with multiple peers, undirected self-loop,
and pattern existence in WHERE clause.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* refactor(lowering): remove dead _build_alias_endpoints function

Superseded by _serialize_binding_ops + orient_edges path from #1008.
The binding_ops code path correctly handles undirected edges by
doubling them (fwd + rev) in EdgeSemantics.orient_edges.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* refactor(pipeline): remove unused undirected peer logic from alias_endpoints path

The alias_endpoints code path in _gfql_bindings_row_table is not called
from the Cypher lowering (which uses binding_ops + orient_edges instead).
The undirected seed/peer detection was added for #994 but never wired up.
Revert to the simpler src/dst join that matches the actual usage.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* docs(changelog): fix #994 description to match actual implementation

The fix uses orient_edges (both-direction doubling), not the removed
alias_endpoints seed/peer detection approach.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* test(cypher): add regression test for parameterized LIMIT in reentry

Covers 992f2fc fix where ParameterRef in LIMIT clause would crash
_literal_limit_value. Verifies WITH...ORDER BY...LIMIT $n...MATCH
reentry is properly rejected with a clear validation error.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant