diff --git a/CHANGELOG.md b/CHANGELOG.md index 087fbf2..99d8894 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +### **0.5.0** (Unreleased) + +#### Features + +- Support for C-style comments in queries with `//` (#31) + ### **0.4.0** (October 17 2023) > Many performance updates, language features, and label support. diff --git a/grandcypher/__init__.py b/grandcypher/__init__.py index 51de6fa..fbc7da7 100644 --- a/grandcypher/__init__.py +++ b/grandcypher/__init__.py @@ -33,7 +33,7 @@ _GrandCypherGrammar = Lark( - """ + r""" start : query query : many_match_clause where_clause return_clause @@ -86,9 +86,9 @@ edge_match : LEFT_ANGLE? "--" RIGHT_ANGLE? | LEFT_ANGLE? "-[]-" RIGHT_ANGLE? - | LEFT_ANGLE? "-[" CNAME "]-" RIGHT_ANGLE? - | LEFT_ANGLE? "-[" CNAME ":" TYPE "]-" RIGHT_ANGLE? - | LEFT_ANGLE? "-[" ":" TYPE "]-" RIGHT_ANGLE? + | LEFT_ANGLE? "-[" CNAME "]-" RIGHT_ANGLE? + | LEFT_ANGLE? "-[" CNAME ":" TYPE "]-" RIGHT_ANGLE? + | LEFT_ANGLE? "-[" ":" TYPE "]-" RIGHT_ANGLE? | LEFT_ANGLE? "-[" "*" MIN_HOP "]-" RIGHT_ANGLE? | LEFT_ANGLE? "-[" "*" MIN_HOP ".." MAX_HOP "]-" RIGHT_ANGLE? | LEFT_ANGLE? "-[" CNAME "*" MIN_HOP "]-" RIGHT_ANGLE? @@ -127,6 +127,8 @@ %import common.WS %ignore WS +COMMENT: "//" /[^\n]/* +%ignore COMMENT """, start="start", @@ -163,7 +165,7 @@ def _is_node_attr_match( host_node = host.nodes[host_node_id] for attr, val in motif_node.items(): - if attr == '__labels__': + if attr == "__labels__": if val and val - host_node.get("__labels__", set()): return False continue @@ -198,7 +200,7 @@ def _is_edge_attr_match( host_edge = host.edges[host_edge_id] for attr, val in motif_edge.items(): - if attr == '__labels__': + if attr == "__labels__": if val and val - host_edge.get("__labels__", set()): return False continue @@ -222,9 +224,7 @@ def _get_entity_from_host(host: nx.DiGraph, entity_name, entity_attribute=None): # looking for an edge: edge_data = host.get_edge_data(*entity_name) if not edge_data: - return ( - None # print(f"Nothing found for {entity_name} {entity_attribute}") - ) + return None # print(f"Nothing found for {entity_name} {entity_attribute}") if entity_attribute: # looking for edge attribute: return edge_data.get(entity_attribute, None) @@ -234,8 +234,10 @@ def _get_entity_from_host(host: nx.DiGraph, entity_name, entity_attribute=None): def _get_edge(host: nx.DiGraph, mapping, match_path, u, v): edge_path = match_path[(u, v)] - return [host.get_edge_data(mapping[u], mapping[v]) - for u, v in zip(edge_path[:-1], edge_path[1:])] + return [ + host.get_edge_data(mapping[u], mapping[v]) + for u, v in zip(edge_path[:-1], edge_path[1:]) + ] CONDITION = Callable[[dict, nx.DiGraph, list], bool] @@ -244,13 +246,17 @@ def _get_edge(host: nx.DiGraph, mapping, match_path, u, v): def and_(cond_a, cond_b) -> CONDITION: def inner(match: dict, host: nx.DiGraph, return_endges: list) -> bool: return cond_a(match, host, return_endges) and cond_b(match, host, return_endges) + return inner + def or_(cond_a, cond_b): def inner(match: dict, host: nx.DiGraph, return_endges: list) -> bool: return cond_a(match, host, return_endges) or cond_b(match, host, return_endges) + return inner + def cond_(should_be, entity_id, operator, value) -> CONDITION: def inner(match: dict, host: nx.DiGraph, return_endges: list) -> bool: host_entity_id = entity_id.split(".") @@ -269,6 +275,7 @@ def inner(match: dict, host: nx.DiGraph, return_endges: list) -> bool: if val != should_be: return False return True + return inner @@ -313,12 +320,14 @@ def _lookup(self, data_paths: List[str], offset_limit) -> Dict[str, List]: 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: raise NotImplementedError(f"Unknown entity name: {data_path}") - + result = {} true_matches = self._get_true_matches() for data_path in data_paths: - entity_name, entity_attribute = _data_path_to_entity_name_attribute(data_path) + entity_name, entity_attribute = _data_path_to_entity_name_attribute( + data_path + ) if entity_name in motif_nodes: # We are looking for a node mapping in the target graph: @@ -329,17 +338,24 @@ def _lookup(self, data_paths: List[str], offset_limit) -> Dict[str, List]: if entity_attribute: # Get the correct entity from the target host graph, # and then return the attribute: - ret = (self._target_graph.nodes[node].get(entity_attribute, None) for node in ret) + ret = ( + self._target_graph.nodes[node].get(entity_attribute, None) + for node in ret + ) else: mapping_u, mapping_v = self._return_edges[data_path] # We are looking for an edge mapping in the target graph: is_hop = self._motif.edges[(mapping_u, mapping_v)]["__is_hop__"] - ret = (_get_edge(self._target_graph, mapping, match_path,mapping_u, mapping_v) - for mapping, match_path in true_matches) + ret = ( + _get_edge( + self._target_graph, mapping, match_path, mapping_u, mapping_v + ) + for mapping, match_path in true_matches + ) ret = (r[0] if is_hop else r for r in ret) # we keep the original list if len > 2 (edge hop 2+) - + if entity_attribute: # Get the correct entity from the target host graph, # and then return the attribute: @@ -377,8 +393,9 @@ def _get_true_matches(self): # TODO: promote these to inside the monomorphism search actual_matches = [] for match, match_path in self._get_structural_matches(): - if (not self._where_condition or - self._where_condition(match, self._target_graph, self._return_edges)): + if not self._where_condition or self._where_condition( + match, self._target_graph, self._return_edges + ): actual_matches.append((match, match_path)) return actual_matches @@ -399,24 +416,28 @@ def _get_structural_matches(self): if (self._skip and self._limit) else None, is_node_attr_match=_is_node_attr_match, - is_edge_attr_match=_is_edge_attr_match + is_edge_attr_match=_is_edge_attr_match, ) if not matches: matches = _matches elif _matches: matches = [{**a, **b} for a in matches for b in _matches] - zero_hop_edges = [k for k, v in edge_hop_map.items() if len(v) == 2 and v[0] == v[1]] + zero_hop_edges = [ + k for k, v in edge_hop_map.items() if len(v) == 2 and v[0] == v[1] + ] for match in matches: # matches can contains zero hop edges from A to B # there are 2 cases to take care # (1) there are both A and B in the match. This case is the result of query A -[*0]-> B --> C. # If A != B break else continue to (2) - # (2) there is only A in the match. This case is the result of query A -[*0]-> B. + # (2) there is only A in the match. This case is the result of query A -[*0]-> B. # If A is qualified to be B (node attr match), set B = A else break for a, b in zero_hop_edges: if b in match and match[b] != match[a]: break - if not _is_node_attr_match(b, match[a], self._motif, self._target_graph): + if not _is_node_attr_match( + b, match[a], self._motif, self._target_graph + ): break match[b] = match[a] else: @@ -425,10 +446,10 @@ def _get_structural_matches(self): self._matches = self_matches self._matche_paths = self_matche_paths return list(zip(self._matches, self._matche_paths)) - + def _edge_hop_motifs(self, motif: nx.DiGraph) -> List[Tuple[nx.Graph, dict]]: """generate a list of edge-hop-expanded motif with edge-hop-map. - + Arguments: motif (nx.Graph): The motif graph @@ -459,7 +480,9 @@ def _edge_hop_motifs(self, motif: nx.DiGraph) -> List[Tuple[nx.Graph, dict]]: for _ in range(max(min_hop, 1), max_hop): new_edges = [u] + hops + [v] new_motif = nx.DiGraph() - new_motif.add_edges_from(list(zip(new_edges[:-1], new_edges[1:])), __labels__ = edge_type) + new_motif.add_edges_from( + list(zip(new_edges[:-1], new_edges[1:])), __labels__=edge_type + ) new_motif.add_node(u, **motif.nodes[u]) new_motif.add_node(v, **motif.nodes[v]) new_motifs.append((new_motif, {(u, v): tuple(new_edges)})) @@ -468,7 +491,10 @@ def _edge_hop_motifs(self, motif: nx.DiGraph) -> List[Tuple[nx.Graph, dict]]: return motifs def _product_motifs( - self, motifs_1: List[Tuple[nx.DiGraph, dict]], motifs_2: List[Tuple[nx.DiGraph, dict]]): + self, + motifs_1: List[Tuple[nx.DiGraph, dict]], + motifs_2: List[Tuple[nx.DiGraph, dict]], + ): new_motifs = [] for motif_1, mapping_1 in motifs_1: for motif_2, mapping_2 in motifs_2: @@ -479,8 +505,7 @@ def _product_motifs( motif.add_edges_from(motif_2.edges.data()) new_motifs.append((motif, {**mapping_1, **mapping_2})) return new_motifs - - + def entity_id(self, entity_id): if len(entity_id) == 2: return ".".join(entity_id) @@ -530,10 +555,12 @@ def match_clause(self, match_clause: Tuple): if len(match_clause) == 1: # This is just a node match: u, ut, js = match_clause[0] - self._motif.add_node(u.value, __labels__ = ut, **js) + self._motif.add_node(u.value, __labels__=ut, **js) return 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] + ((u, ut, ujs), (g, t, d, minh, maxh), (v, vt, vjs)) = match_clause[ + start : start + 3 + ] if d == "r": edges = ((u.value, v.value),) elif d == "l": @@ -542,10 +569,10 @@ def match_clause(self, match_clause: Tuple): edges = ((u.value, v.value), (v.value, u.value)) else: raise ValueError(f"Not support direction d={d!r}") - + if g: self._return_edges[g.value] = edges[0] - + ish = minh is None and maxh is None minh = minh if minh is not None else 1 maxh = maxh if maxh is not None else minh + 1 @@ -554,14 +581,15 @@ def match_clause(self, match_clause: Tuple): if t: t = set([t]) self._motif.add_edges_from( - edges, __min_hop__ = minh, __max_hop__ = maxh, __is_hop__ = ish, __labels__ = t) - + edges, __min_hop__=minh, __max_hop__=maxh, __is_hop__=ish, __labels__=t + ) + self._motif.add_node(u, __labels__=ut, **ujs) self._motif.add_node(v, __labels__=vt, **vjs) def where_clause(self, where_clause: tuple): self._where_condition = where_clause[0] - + def compound_condition(self, val): if len(val) == 1: val = cond_(*val[0]) @@ -573,7 +601,6 @@ def compound_condition(self, val): def where_and(self, val): return _BOOL_ARI["and"] - def where_or(self, val): return _BOOL_ARI["or"] @@ -632,7 +659,6 @@ class GrandCypher: """ def __init__(self, host_graph: nx.Graph) -> None: - """ Create a new GrandCypher object to query graphs with Cypher. diff --git a/grandcypher/test_parser.py b/grandcypher/test_parser.py index 4586ed9..0b68c89 100644 --- a/grandcypher/test_parser.py +++ b/grandcypher/test_parser.py @@ -152,7 +152,7 @@ def test_simple_api_single_node_multi_where_2(self): """ assert len(GrandCypher(host).run(qry)["A"]) == 2 - + def test_null_where(self): host = nx.DiGraph() host.add_node("x", foo="foo") @@ -166,7 +166,6 @@ def test_null_where(self): """ assert len(GrandCypher(host).run(qry)["A.foo"]) == 2 - def test_simple_api_multi_node_multi_where(self): host = nx.DiGraph() host.add_edge("x", "y") @@ -204,7 +203,7 @@ def test_simple_api_anonymous_edge(self): """ assert len(GrandCypher(host).run(qry)["A"]) == 3 - + def test_simple_api_anonymous_node(self): host = nx.DiGraph() host.add_edge("x", "y") @@ -317,7 +316,7 @@ def test_node_dict(self): host.add_edge("X", "Z", type="bar") assert len(GrandCypher(host).run(qry)["A"]) == 1 - + def test_null_value(self): host = nx.DiGraph() host.add_node("x", foo="foo") @@ -421,7 +420,7 @@ def test_left_or_right_direction_with_where(self): host.add_edge("z", "y") qry = """Match (A{name:"x"}) -[AB]-> (B) return B.name""" - res = GrandCypher(host).run(qry) + res = GrandCypher(host).run(qry) assert len(res) == 1 assert list(res.values())[0] == ["y"] @@ -459,21 +458,27 @@ def test_chained_edges(self): host.add_edge("x", "y") host.add_edge("y", "z") - qry = """Match (A{name:"x"}) -[]-> (B) -[]-> (C) return A.name, B.name, C.name""" + qry = ( + """Match (A{name:"x"}) -[]-> (B) -[]-> (C) return A.name, B.name, C.name""" + ) res = GrandCypher(host).run(qry) assert len(res) == 3 assert res["A.name"] == ["x"] assert res["B.name"] == ["y"] assert res["C.name"] == ["z"] - qry = """Match (A{name:"y"}) -[]-> (B) -[]-> (C) return A.name, B.name, C.name""" + qry = ( + """Match (A{name:"y"}) -[]-> (B) -[]-> (C) return A.name, B.name, C.name""" + ) res = GrandCypher(host).run(qry) assert len(res) == 3 assert res["A.name"] == [] assert res["B.name"] == [] assert res["C.name"] == [] - qry = """Match (A) -[]-> (B{name:"y"}) -[]-> (C) return A.name, B.name, C.name""" + qry = ( + """Match (A) -[]-> (B{name:"y"}) -[]-> (C) return A.name, B.name, C.name""" + ) res = GrandCypher(host).run(qry) assert len(res) == 3 assert res["A.name"] == ["x"] @@ -495,14 +500,18 @@ def test_chained_backward_edges(self): host.add_edge("x", "y") host.add_edge("z", "y") - qry = """Match (A{name:"x"}) -[]-> (B) -[]-> (C) return A.name, B.name, C.name""" + qry = ( + """Match (A{name:"x"}) -[]-> (B) -[]-> (C) return A.name, B.name, C.name""" + ) res = GrandCypher(host).run(qry) assert len(res) == 3 assert res["A.name"] == [] assert res["B.name"] == [] assert res["C.name"] == [] - qry = """Match (A) -[]-> (B{name:"y"}) -[]-> (C) return A.name, B.name, C.name""" + qry = ( + """Match (A) -[]-> (B{name:"y"}) -[]-> (C) return A.name, B.name, C.name""" + ) res = GrandCypher(host).run(qry) assert len(res) == 3 assert res["A.name"] == [] @@ -516,14 +525,18 @@ def test_chained_backward_edges(self): assert res["B.name"] == [] assert res["C.name"] == [] - qry = """Match (A{name:"x"}) -[]-> (B) <-[]- (C) return A.name, B.name, C.name""" + qry = ( + """Match (A{name:"x"}) -[]-> (B) <-[]- (C) return A.name, B.name, C.name""" + ) res = GrandCypher(host).run(qry) assert len(res) == 3 assert res["A.name"] == ["x"] assert res["B.name"] == ["y"] assert res["C.name"] == ["z"] - qry = """Match (A) -[]-> (B{name:"y"}) <-[]- (C) return A.name, B.name, C.name""" + qry = ( + """Match (A) -[]-> (B{name:"y"}) <-[]- (C) return A.name, B.name, C.name""" + ) res = GrandCypher(host).run(qry) assert len(res) == 3 assert res["A.name"] == ["x", "z"] @@ -563,7 +576,7 @@ def test_undirected(self): assert len(res) == 2 assert res["A.name"] == ["x"] assert res["B.name"] == ["y"] - + def test_anonymous_node(self): host = nx.DiGraph() host.add_node("x", name="x") @@ -574,7 +587,7 @@ def test_anonymous_node(self): host.add_edge("z", "y") qry = """Match () -[]-> (B) <-[]- () return B.name""" - res = GrandCypher(host).run(qry) + res = GrandCypher(host).run(qry) assert len(res) == 1 assert res["B.name"] == ["y", "y"] @@ -639,8 +652,10 @@ def test_single_variable_length_relationship(self): assert res["A"] == ["x", "y", "z"] assert res["B"] == ["z", "x", "y"] assert res["r"] == [ - [{"bar": "1"}, {"bar": "2"}], [{"bar": "2"}, {"bar": "3"}], [{"bar": "3"}, {"bar": "1"}]] - + [{"bar": "1"}, {"bar": "2"}], + [{"bar": "2"}, {"bar": "3"}], + [{"bar": "3"}, {"bar": "1"}], + ] def test_complex_variable_length_relationship(self): host = nx.DiGraph() @@ -661,13 +676,19 @@ def test_complex_variable_length_relationship(self): assert res["A"] == ["x", "y", "z", "x", "y", "z", "x", "y", "z"] assert res["B"] == ["x", "y", "z", "y", "z", "x", "z", "x", "y"] assert res["r"] == [ - [None], [None], [None], - [{"bar": "1"}], [{"bar": "2"}], [{"bar": "3"}], - [{"bar": "1"}, {"bar": "2"}], [{"bar": "2"}, {"bar": "3"}], [{"bar": "3"}, {"bar": "1"}]] - + [None], + [None], + [None], + [{"bar": "1"}], + [{"bar": "2"}], + [{"bar": "3"}], + [{"bar": "1"}, {"bar": "2"}], + [{"bar": "2"}, {"bar": "3"}], + [{"bar": "3"}, {"bar": "1"}], + ] -class TestType(): +class TestType: def test_host_no_edge_type(self): host = nx.DiGraph() host.add_node("x") @@ -767,9 +788,15 @@ def test_edge_type_hop(self): assert res["A"] == ["x", "y", "z", "x", "y", "z", "x", "y", "z"] assert res["B"] == ["x", "y", "z", "y", "z", "x", "z", "x", "y"] assert res["r"] == [ - [None], [None], [None], - [{'__labels__': {'Edge', 'XY'}}], [{'__labels__': {'Edge', 'YZ'}}], [{'__labels__': {'Edge', 'ZX'}}], - [{'__labels__': {'Edge', 'XY'}}, {'__labels__': {'Edge', 'YZ'}}], [{'__labels__': {'Edge', 'YZ'}}, {'__labels__': {'Edge', 'ZX'}}], [{'__labels__': {'Edge', 'ZX'}}, {'__labels__': {'Edge', 'XY'}}] + [None], + [None], + [None], + [{"__labels__": {"Edge", "XY"}}], + [{"__labels__": {"Edge", "YZ"}}], + [{"__labels__": {"Edge", "ZX"}}], + [{"__labels__": {"Edge", "XY"}}, {"__labels__": {"Edge", "YZ"}}], + [{"__labels__": {"Edge", "YZ"}}, {"__labels__": {"Edge", "ZX"}}], + [{"__labels__": {"Edge", "ZX"}}, {"__labels__": {"Edge", "XY"}}], ] def test_host_no_node_type(self): @@ -793,9 +820,9 @@ def test_host_no_node_type(self): def test_node_type(self): host = nx.DiGraph() - host.add_node("x", __labels__ = set(["Node", "X"]), foo="1") - host.add_node("y", __labels__ = set(["Node", "Y"]), foo="2") - host.add_node("z", __labels__ = set(["Node", "Z"]), foo="3") + host.add_node("x", __labels__=set(["Node", "X"]), foo="1") + host.add_node("y", __labels__=set(["Node", "Y"]), foo="2") + host.add_node("z", __labels__=set(["Node", "Z"]), foo="3") host.add_edge("x", "y") host.add_edge("y", "z") host.add_edge("z", "x") @@ -833,9 +860,9 @@ def test_node_type(self): def test_node_type_edge_hop(self): host = nx.DiGraph() - host.add_node("x", __labels__ = set(["Node", "X"]), foo="1") - host.add_node("y", __labels__ = set(["Node", "Y"]), foo="2") - host.add_node("z", __labels__ = set(["Node", "Z"]), foo="3") + host.add_node("x", __labels__=set(["Node", "X"]), foo="1") + host.add_node("y", __labels__=set(["Node", "Y"]), foo="2") + host.add_node("z", __labels__=set(["Node", "Z"]), foo="3") host.add_edge("x", "y") host.add_edge("y", "z") @@ -872,27 +899,26 @@ def test_node_type_edge_hop(self): class TestSpecialCases: - def test_two_edge_hop_with_edge_node_type(self): host = nx.DiGraph() - host.add_node("C_1_1", __labels__ = set(["X"]), head=True) - host.add_node("C_1_2", __labels__ = set(["X"])) - host.add_node("C_1_3", __labels__ = set(["X"])) - host.add_node("C_2_1", name="C_2_1", __labels__ = set(["X"]), head=True) - host.add_node("C_2_2", __labels__ = set(["X"])) - host.add_edge("C_1_1", "C_1_2", __labels__ = set(["b"])) - host.add_edge("C_1_2", "C_1_3", __labels__ = set(["b"])) - host.add_edge("C_2_1", "C_2_2", __labels__ = set(["b"])) - - host.add_node("a_1_1", __labels__ = set(["Y"]), head=True) - host.add_node("a_1_2", __labels__ = set(["Y"])) - host.add_node("a_2_1", __labels__ = set(["Y"]), head=True) - host.add_edge("a_1_1", "a1_2", __labels__ = set(["b"])) - - host.add_edge("C_1_1", "a_1_1", __labels__ = set(["i"])) - host.add_edge("C_1_3", "a_1_2", __labels__ = set(["i"])) - host.add_edge("C_1_2", "a_2_1", __labels__ = set(["i"])) - host.add_edge("C_2_2", "a_2_1", __labels__ = set(["i"])) + host.add_node("C_1_1", __labels__=set(["X"]), head=True) + host.add_node("C_1_2", __labels__=set(["X"])) + host.add_node("C_1_3", __labels__=set(["X"])) + host.add_node("C_2_1", name="C_2_1", __labels__=set(["X"]), head=True) + host.add_node("C_2_2", __labels__=set(["X"])) + host.add_edge("C_1_1", "C_1_2", __labels__=set(["b"])) + host.add_edge("C_1_2", "C_1_3", __labels__=set(["b"])) + host.add_edge("C_2_1", "C_2_2", __labels__=set(["b"])) + + host.add_node("a_1_1", __labels__=set(["Y"]), head=True) + host.add_node("a_1_2", __labels__=set(["Y"])) + host.add_node("a_2_1", __labels__=set(["Y"]), head=True) + host.add_edge("a_1_1", "a1_2", __labels__=set(["b"])) + + host.add_edge("C_1_1", "a_1_1", __labels__=set(["i"])) + host.add_edge("C_1_3", "a_1_2", __labels__=set(["i"])) + host.add_edge("C_1_2", "a_2_1", __labels__=set(["i"])) + host.add_edge("C_2_2", "a_2_1", __labels__=set(["i"])) qry = """ MATCH (A:X) -[:b*0..5]-> (B:X) -[:i*0..1]-> (c) @@ -902,13 +928,127 @@ def test_two_edge_hop_with_edge_node_type(self): res = GrandCypher(host).run(qry) assert len(res) == 3 - + C_1_indices = [i for i, v in enumerate(res["A"]) if v == "C_1_1"] C_2_indices = [i for i, v in enumerate(res["A"]) if v == "C_2_1"] assert len(C_1_indices) + len(C_2_indices) == len(res["A"]) - + assert set(res["B"][i] for i in C_1_indices) == set(["C_1_1", "C_1_2", "C_1_3"]) - assert set(res["c"][i] for i in C_1_indices) == set(["C_1_1", "C_1_2", "C_1_3", "a_1_1", "a_1_2", "a_2_1"]) - + assert set(res["c"][i] for i in C_1_indices) == set( + ["C_1_1", "C_1_2", "C_1_3", "a_1_1", "a_1_2", "a_2_1"] + ) + assert set(res["B"][i] for i in C_2_indices) == set(["C_2_1", "C_2_2"]) assert set(res["c"][i] for i in C_2_indices) == set(["C_2_1", "C_2_2", "a_2_1"]) + + +class TestComments: + def test_line_comments(self): + host = nx.DiGraph() + host.add_node("x", foo=12) + host.add_node("y", foo=13) + host.add_node("z", foo=16) + host.add_edge("x", "y", bar="1") + host.add_edge("y", "z", bar="2") + host.add_edge("z", "x", bar="3") + + qry = """ + // This is a comment + MATCH (A)-[r*0]->(B) + RETURN A, B, r + """ + + res = GrandCypher(host).run(qry) + assert len(res) == 3 + + qry = """ + MATCH (A)-[r*1]->(B) + // This is a comment + RETURN A, B, r + """ + + res = GrandCypher(host).run(qry) + assert len(res) == 3 + + qry = """ + MATCH (A)-[r*2]->(B) + RETURN A, B, r + // This is a comment + """ + + res = GrandCypher(host).run(qry) + + def test_end_of_line_comments(self): + host = nx.DiGraph() + host.add_node("x", foo=12) + host.add_node("y", foo=13) + host.add_node("z", foo=16) + host.add_edge("x", "y", bar="1") + host.add_edge("y", "z", bar="2") + host.add_edge("z", "x", bar="3") + + qry = """ + MATCH (A)-[r*0]->(B) // This is a comment + RETURN A, B, r + """ + + res = GrandCypher(host).run(qry) + assert len(res) == 3 + + qry = """ + MATCH (A)-[r*1]->(B) + RETURN A, B, r // This is a comment + """ + + res = GrandCypher(host).run(qry) + assert len(res) == 3 + + qry = """ + MATCH (A)-[r*2]->(B) + RETURN A, B, r + """ + + res = GrandCypher(host).run(qry) + + def test_every_line_comments(self): + host = nx.DiGraph() + host.add_node("x", foo=12) + host.add_node("y", foo=13) + host.add_node("z", foo=16) + host.add_edge("x", "y", bar="1") + host.add_edge("y", "z", bar="2") + host.add_edge("z", "x", bar="3") + + qry = """ + // This is a comment + MATCH (A)-[r*0]->(B) // This is a comment + RETURN A, B, r // This is a comment + // This is a comment + """ + + res = GrandCypher(host).run(qry) + assert len(res) == 3 + + def test_mid_query_comment(self): + host = nx.DiGraph() + host.add_node("x", foo=12) + host.add_node("y", foo=13) + host.add_node("z", foo=16) + host.add_edge("x", "y", bar="1") + host.add_edge("y", "z", bar="2") + host.add_edge("z", "x", bar="3") + + qry = """ + MATCH + // OBTRUSIVE COMMENT! + (A)-[ + // This is a comment // + r*0 // This is a comment + ]->(B + // Comment! + ) + RETURN A, B, r + """ + + res = GrandCypher(host).run(qry) + assert len(res) == 3