Skip to content

feat(cypher): support variable-length rels in connected patterns (#973)#995

Merged
lmeyerov merged 19 commits intomasterfrom
feat/cypher-multihop-connected
Apr 1, 2026
Merged

feat(cypher): support variable-length rels in connected patterns (#973)#995
lmeyerov merged 19 commits intomasterfrom
feat/cypher-multihop-connected

Conversation

@lmeyerov
Copy link
Copy Markdown
Contributor

Summary

Support variable-length relationships in connected multi-relationship patterns (#973). Previously rejected, now MATCH (a)-[:R*2]->()-[:R]->(c) RETURN c works.

Before

MATCH (a)-[:LIKES*2]->()-[:LIKES]->(c) RETURN c.name
→ GFQLValidationError: only supported as the only relationship in a connected pattern

After

g.gfql("MATCH (a)-[:LIKES*2]->()-[:LIKES]->(c) RETURN c.name")  # exact hops
g.gfql("MATCH (a)-[:R*1..3]->()-[:R]->(c) RETURN c.id")          # range hops
g.gfql("MATCH (a)-[:R]->()-[:R*2]->()-[:R]->(c) RETURN c.id")    # middle position
g.gfql("MATCH (e)<-[:R*2]-()<-[:R]-(c) RETURN c.id")             # reverse

Implementation

Add prune_to_endpoints flag on ASTEdge that filters the graph to only the variable-length hop's endpoint wavefront after traversal. Uses edge hop labels to identify max-distance edges, then prunes nodes + edges to only those endpoints. The lowering sets this flag on non-terminal variable-length relationships in connected patterns.

Tests (11 new)

  • Exact hops at start (*1, *2, *3 then fixed)
  • Range hops (*1..2, *1..3 then fixed)
  • Fixed then varlen
  • Varlen in middle (fixed-*2-fixed)
  • Branching graph (star topology)
  • Reverse direction
  • No match (empty result)
  • Open range (*0.., *1.. then fixed)

GPU validation

All pass on pandas + cudf (dgx-spark, graphistry/test-gpu:latest).

Test plan

  • 450 lowering tests (11 new, 0 regressions)
  • GPU: pandas + cudf on dgx-spark
  • Adversarial: 14/15 cases pass (1 pre-existing empty graph issue)

🤖 Generated with Claude Code

lmeyerov and others added 15 commits March 31, 2026 17:22
MATCH (a)-[:R*2]->()-[:R]->(c) RETURN c now works.

Implementation: add prune_to_endpoints flag on ASTEdge that filters
the graph to only max-distance endpoint nodes after a variable-length
hop. The lowering sets this flag on non-terminal variable-length
relationships in connected patterns so the next hop starts from the
correct intermediate nodes only.

Key changes:
- ast.py: prune_to_endpoints on ASTEdge + all subclasses; edge pruning
  in __call__ using edge hop labels to find max-distance endpoints
- lowering.py: _lower_relationship passes prune_to_endpoints=True for
  non-terminal varlen rels; guard function made no-op

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix prune_to_endpoints for range hops (*1..2) — keep all distances
in the min..max range, not just the max distance. Fix open-range (*0..)
pruning. Add empty edge guard to avoid merge errors on empty graphs.

Adversarial test results: 14/15 cases pass (empty graph is pre-existing).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…log (#973)

Add 11 parametrized tests for connected variable-length patterns:
- Exact hops (*1, *2, *3) then fixed
- Range hops (*1..2, *1..3) then fixed
- Fixed then varlen
- Varlen in middle (fixed-*2-fixed)
- Branching graph, reverse direction, no match

GPU validation: all pass on pandas + cudf (dgx-spark).
450 total lowering tests, 0 regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-existing mypy error: self._source/self._destination are Optional[str]
but used as dict keys. Add assertion since the retry path requires them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lmeyerov lmeyerov force-pushed the feat/cypher-multihop-connected branch from 529a2e9 to 6b17cf6 Compare April 1, 2026 00:33
@lmeyerov lmeyerov closed this Apr 1, 2026
@lmeyerov lmeyerov deleted the feat/cypher-multihop-connected branch April 1, 2026 01:10
@lmeyerov lmeyerov restored the feat/cypher-multihop-connected branch April 1, 2026 01:10
@lmeyerov lmeyerov reopened this Apr 1, 2026
lmeyerov and others added 4 commits March 31, 2026 18:17
…rlen test

The Python 3.14 minimal test suite takes >8 minutes on hosted runners,
causing timeout failures. Bump to 12 minutes for headroom.

Add test for typed variable-length followed by different-type fixed hop
(e.g. MATCH (s)-[:A*2]->()-[:B]->(c)).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Our varlen connected patterns entry was incorrectly merged into 0.53.10
during rebase conflict resolution. Move to Development since it's not
yet on master.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lmeyerov lmeyerov merged commit ffc5d35 into master Apr 1, 2026
66 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant