Skip to content

Commit 2f0d183

Browse files
lmeyerovclaude
andcommitted
feat(cypher): support *0.. open-range variable-length relationships (#983)
Add rel_range_open_max grammar rule and transformer for open-range syntax like *0.., *1.., *2.. where the max bound is unbounded. Handles min_hops=0 specifically (zero-hop means include the seed node itself) while keeping the existing rejection of *0 exact syntax. Adds 4 parametrized parser test cases: *0.., *1.., [:R*0..], [:R*2..]. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2d612f3 commit 2f0d183

2 files changed

Lines changed: 22 additions & 0 deletions

File tree

graphistry/compute/gfql/cypher/parser.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
108108
rel_types: ":" LABEL_NAME ("|" ":"? LABEL_NAME)*
109109
rel_range: "*" INT ".." INT -> rel_range_bounded
110+
| "*" INT ".." -> rel_range_open_max
110111
| "*" INT -> rel_range_exact
111112
| "*" -> rel_range_fixed
112113
@@ -725,6 +726,23 @@ def rel_range_bounded(self, meta: Any, items: Sequence[Any]) -> dict[str, Any]:
725726
)
726727
return {"min_hops": min_hops, "max_hops": max_hops, "to_fixed_point": False}
727728

729+
def rel_range_open_max(self, meta: Any, items: Sequence[Any]) -> dict[str, Any]:
730+
if len(items) != 1:
731+
raise _to_syntax_error("Invalid relationship range", line=meta.line, column=meta.column)
732+
try:
733+
value = int(str(items[0]))
734+
except Exception as exc:
735+
raise _to_syntax_error("Invalid relationship range bound", line=meta.line, column=meta.column) from exc
736+
if value < 0:
737+
raise _to_unsupported(
738+
"Cypher negative-hop relationship ranges are not supported",
739+
line=meta.line,
740+
column=meta.column,
741+
field="match",
742+
value=self._slice(_span_from_meta(meta)),
743+
)
744+
return {"min_hops": value, "max_hops": None, "to_fixed_point": True}
745+
728746
def rel_range_fixed(self, meta: Any, _items: Sequence[Any]) -> dict[str, Any]:
729747
return {"min_hops": None, "max_hops": None, "to_fixed_point": True}
730748

graphistry/tests/compute/gfql/cypher/test_parser.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,10 @@ def test_parse_relationship_type_alternation_with_repeated_colon() -> None:
439439
("MATCH (a)<-[*4]-(b) RETURN b", "reverse", 4, 4, False, ()),
440440
("MATCH (a)-[*]->(b) RETURN b", "forward", None, None, True, ()),
441441
("MATCH (a)-[:R|:S*2..4]-(b) RETURN b", "undirected", 2, 4, False, ("R", "S")),
442+
("MATCH (a)-[*0..]->(b) RETURN b", "forward", 0, None, True, ()),
443+
("MATCH (a)-[*1..]->(b) RETURN b", "forward", 1, None, True, ()),
444+
("MATCH (a)-[:R*0..]-(b) RETURN b", "undirected", 0, None, True, ("R",)),
445+
("MATCH (a)<-[:R*2..]-(b) RETURN b", "reverse", 2, None, True, ("R",)),
442446
],
443447
)
444448
def test_parse_variable_length_relationship_patterns(

0 commit comments

Comments
 (0)