import re class Node: def __init__(self, node_id, labels=None, properties=None): self.id = node_id self.labels = set(labels or []) self.properties = properties or {} class Relationship: def __init__(self, from_node, to_node, rel_type, properties=None): self.from_node = from_node self.to_node = to_node self.type = rel_type self.properties = properties or {} class GraphDB: def __init__(self): self.nodes = {} # node_id -> Node self.relationships = [] # list of Relationship objects def add_node(self, node_id, labels=None, **properties): self.nodes[node_id] = Node(node_id, labels, properties) def add_relationship(self, from_node, to_node, rel_type, **properties): if from_node in self.nodes and to_node in self.nodes: self.relationships.append(Relationship(from_node, to_node, rel_type, properties)) def match(self, label=None, property_key=None, property_value=None): return [ node for node in self.nodes.values() if (not label or label in node.labels) and (not property_key or node.properties.get(property_key) == property_value) ] def match_relationship(self, rel_type=None, from_node=None, to_node=None): return [ rel for rel in self.relationships if (not rel_type or rel.type == rel_type) and (not from_node or rel.from_node == from_node) and (not to_node or rel.to_node == to_node) ] def parse_properties(prop_str): props = {} for pair in re.findall(r"(\w+):\s*('([^']*)'|\d+|true|false)", prop_str): key, raw_val, str_val = pair if raw_val.startswith("'"): props[key] = str_val elif raw_val in ['true', 'false']: props[key] = raw_val == 'true' else: props[key] = int(raw_val) return props def parse_where_clause(where_str): comparisons = re.findall(r"(\w+)\.(\w+)\s*(=|>|<|>=|<=)\s*('([^']*)'|\d+|true|false)", where_str) filters = [] for var, key, op, raw_val, str_val in comparisons: if raw_val.startswith("'"): val = str_val elif raw_val in ['true', 'false']: val = raw_val == 'true' else: val = int(raw_val) filters.append((key, op, val)) return filters class CypherEngine: def __init__(self, graph): self.graph = graph def run(self, query): # Basic MATCH node pattern: MATCH (n:Label {key: value}) RETURN n match_node = re.match(r"MATCH\s+\((\w+):(\w+)\s*\{(\w+):\s*'([^']+)'\}\)\s+RETURN\s+\1", query) if match_node: var, label, key, value = match_node.groups() return self.graph.match(label=label, property_key=key, property_value=value) # (e.g., MATCH (n:Person {name: 'Alice', age: 30}) RETURN n) match_node = re.match(r"MATCH\s+\((\w+):(\w+)\s*\{([^}]+)\}\)\s+RETURN\s+\1", query) if match_node: var, label, prop_str = match_node.groups() props = GraphDB.parse_properties(prop_str) return self.graph.match(label=label, **props) # (e.g., MATCH (n:Person) WHERE n.age > 25 RETURN n) match_node = re.match(r"MATCH\s+\((\w+):(\w+)\)\s*(?:WHERE\s+(.+?))?\s+RETURN\s+\1", query) if match_node: var, label, where_str = match_node.groups() nodes = self.graph.match(label=label) if where_str: filters = GraphDB.parse_where_clause(where_str) def passes(node): for key, op, val in filters: node_val = node.properties.get(key) if node_val is None: return False if op == '=' and node_val != val: return False if op == '>' and not node_val > val: return False if op == '<' and not node_val < val: return False if op == '>=' and not node_val >= val: return False if op == '<=' and not node_val <= val: return False return True nodes = [n for n in nodes if passes(n)] return nodes # Basic MATCH relationship pattern: MATCH (a)-[:TYPE]->(b) RETURN a,b match_rel = re.match(r"MATCH\s+\((\w+)\)-\[:(\w+)\]->\((\w+)\)\s+RETURN\s+\1,\s*\3", query) # MATCH (a:Person)-[:FRIEND]->(b:Person) RETURN a,b match_rel = re.match(r"MATCH\s+\((\w+):(\w+)?\)-\[:(\w+)\]->\((\w+):(\w+)?\)\s+RETURN\s+\1,\s*\4", query) if match_rel: from_var, from_label, rel_type, to_var, to_label = match_rel.groups() rels = self.graph.match_relationship(rel_type=rel_type) return [ rel for rel in rels if (not from_label or from_label in self.graph.nodes[rel.from_node].labels) and (not to_label or to_label in self.graph.nodes[rel.to_node].labels) ] return "Unsupported query format" # Example usage and test cases g = GraphDB() g.add_node("A", labels=["Person"], name="Alice", age=30) g.add_node("B", labels=["Person"], name="Bob", age=25) g.add_node("C", labels=["City"], name="Phoenix") g.add_relationship("A", "B", "FRIEND", since=2020) g.add_relationship("A", "C", "LIVES_IN") # Match nodes with label 'Person' people = g.match(label="Person") # Match relationships of type 'FRIEND' friendships = g.match_relationship(rel_type="FRIEND") engine = CypherEngine(g) result1 = engine.run("MATCH (n:Person {name: 'Alice'}) RETURN n") result2 = engine.run("MATCH (n:Person) WHERE n.age > 25 RETURN n") result3 = engine.run("MATCH (a:Person)-[:FRIEND]->(b:Person) RETURN a,b") print("People:", [(p.id, p.properties) for p in people]) print("Friendships:", [ (rel.from_node, rel.to_node, rel.properties) for rel in friendships]) print("Result 1:", [(n.id, n.properties) for n in result1]) print("Result 2:", [(n.id, n.properties) for n in result2]) print("Result 3:", [ (rel.from_node, rel.to_node, rel.properties) for rel in result3]) # Expected Output: # People: [('A', {'name': 'Alice', 'age': 30}), ('B', {'name': 'Bob', 'age': 25})] # Friendships: [('A', 'B', {'since': 2020})] # Result 1: [('A', {'name': 'Alice', 'age': 30})] # Result 2: [('A', {'name': 'Alice', 'age': 30))] # Result 3: [('A', 'B', {'since': 2020})]