Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add path groups #37

Merged
merged 4 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# CHANGELOG

### **0.5.0** (Unreleased)
### **0.6.0** (February 15 2024)

> New path group operator

#### Features

- Support for path group operators (#37)

### **0.5.0** (February 13 2024)

> Lots of language support for new query operators.

Expand Down
30 changes: 26 additions & 4 deletions grandcypher/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@
many_match_clause : (match_clause)+


match_clause : "match"i node_match (edge_match node_match)*
match_clause : "match"i path_clause? node_match (edge_match node_match)*

path_clause : CNAME EQUAL

where_clause : "where"i compound_condition

Expand Down Expand Up @@ -111,6 +113,7 @@

LEFT_ANGLE : "<"
RIGHT_ANGLE : ">"
EQUAL : "="
MIN_HOP : INT
MAX_HOP : INT
TYPE : CNAME
Expand Down Expand Up @@ -309,6 +312,7 @@ def _data_path_to_entity_name_attribute(data_path):
class _GrandCypherTransformer(Transformer):
def __init__(self, target_graph: nx.Graph, limit=None):
self._target_graph = target_graph
self._paths = []
self._where_condition: CONDITION = None
self._motif = nx.DiGraph()
self._matches = None
Expand All @@ -327,7 +331,7 @@ def _lookup(self, data_paths: List[str], offset_limit) -> Dict[str, List]:

for data_path in data_paths:
entity_name, _ = _data_path_to_entity_name_attribute(data_path)
if entity_name not in motif_nodes and entity_name not in self._return_edges:
if entity_name not in motif_nodes and entity_name not in self._return_edges and entity_name not in self._paths:
raise NotImplementedError(f"Unknown entity name: {data_path}")

result = {}
Expand All @@ -352,6 +356,20 @@ def _lookup(self, data_paths: List[str], offset_limit) -> Dict[str, List]:
for node in ret
)

elif entity_name in self._paths:
ret = []
for mapping, _ in true_matches:
path, nodes = [], list(mapping.values())
for x, node in enumerate(nodes):
# Edge
if x > 0:
path.append(self._target_graph.get_edge_data(nodes[x - 1], node))

# Node
path.append(node)

ret.append(path)

else:
mapping_u, mapping_v = self._return_edges[data_path]
# We are looking for an edge mapping in the target graph:
Expand Down Expand Up @@ -460,8 +478,7 @@ def _matches_iter(self, motif):

# Single match clause iterator
if iterators and len(iterators) == 1:
for x, match in enumerate(iterators[0]):
yield match
yield from iterators[0]

# Multi match clause, requires a cartesian join
else:
Expand Down Expand Up @@ -603,6 +620,8 @@ def match_clause(self, match_clause: Tuple):
u, ut, js = match_clause[0]
self._motif.add_node(u.value, __labels__=ut, **js)
return

match_clause = match_clause[1:] if not match_clause[0] else match_clause
for start in range(0, len(match_clause) - 2, 2):
((u, ut, ujs), (g, t, d, minh, maxh), (v, vt, vjs)) = match_clause[
start : start + 3
Expand Down Expand Up @@ -633,6 +652,9 @@ def match_clause(self, match_clause: Tuple):
self._motif.add_node(u, __labels__=ut, **ujs)
self._motif.add_node(v, __labels__=vt, **vjs)

def path_clause(self, path_clause: tuple):
self._paths.append(path_clause[0])

def where_clause(self, where_clause: tuple):
self._where_condition = where_clause[0]

Expand Down
20 changes: 20 additions & 0 deletions grandcypher/test_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -1157,3 +1157,23 @@ def test_nested_nots_in_statements(self):

res = GrandCypher(host).run(qry)
assert len(res["Instrument"]) == 1


class TestPath:
def test_path(self):
host = nx.DiGraph()
host.add_node("x", name="x")
host.add_node("y", name="y")
host.add_node("z", name="z")

host.add_edge("x", "y", foo="bar")
host.add_edge("y", "z",)

qry = """
MATCH P = ()-[r*2]->()
RETURN P
LIMIT 1
"""

res = GrandCypher(host).run(qry)
assert len(res["P"][0]) == 5
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="grand-cypher",
version="0.5.0",
version="0.6.0",
author="Jordan Matelsky",
author_email="[email protected]",
description="Query Grand graphs using Cypher",
Expand Down
Loading