neo4jstate.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. from typing import Any, Optional, List, Tuple, Callable, Generator
  2. from neo4j import GraphDatabase
  3. from ast import literal_eval
  4. from .base import State, Edge, Node, Element, UUID
  5. class Neo4jState(State):
  6. def __init__(self, uri="bolt://localhost:7687", user="neo4j", password="tests"):
  7. self.driver = GraphDatabase.driver(uri, auth=(user, password))
  8. self.root = self.create_node()
  9. def close(self, *, clear=False):
  10. if clear:
  11. self._run_and_return(self._clear)
  12. self.driver.close()
  13. def _run_and_return(self, query: Callable, **kwargs):
  14. with self.driver.session() as session:
  15. result = session.write_transaction(query, **kwargs)
  16. return result
  17. @staticmethod
  18. def _clear(tx):
  19. tx.run("MATCH (n) "
  20. "DETACH DELETE n")
  21. @staticmethod
  22. def _existence_check(tx, eid, label="Element"):
  23. result = tx.run(f"MATCH (elem:{label}) "
  24. "WHERE elem.id = $eid "
  25. "RETURN elem.id",
  26. eid=eid)
  27. try:
  28. return result.single()[0]
  29. except TypeError:
  30. # No node found for nid
  31. # ergo, no edge created
  32. return None
  33. def create_node(self) -> Node:
  34. def query(tx, nid):
  35. result = tx.run("CREATE (n:Element:Node) "
  36. "SET n.id = $nid "
  37. "RETURN n.id",
  38. nid=nid)
  39. return result.single()[0]
  40. node = self._run_and_return(query, nid=str(self.new_id()))
  41. return UUID(node) if node is not None else None
  42. def create_edge(self, source: Element, target: Element) -> Optional[Edge]:
  43. def query(tx, eid, sid, tid):
  44. result = tx.run("MATCH (source), (target) "
  45. "WHERE source.id = $sid AND target.id = $tid "
  46. "CREATE (source) -[:Source]-> (e:Element:Edge) -[:Target]-> (target) "
  47. "SET e.id = $eid "
  48. "RETURN e.id",
  49. eid=eid, sid=sid, tid=tid)
  50. try:
  51. return result.single()[0]
  52. except TypeError:
  53. # No node found for sid and/or tid
  54. # ergo, no edge created
  55. return None
  56. edge = self._run_and_return(query, eid=str(self.new_id()), sid=str(source), tid=str(target))
  57. return UUID(edge) if edge is not None else None
  58. def create_nodevalue(self, value: Any) -> Optional[Node]:
  59. def query(tx, nid, val):
  60. result = tx.run("CREATE (n:Element:Node) "
  61. "SET n.id = $nid, n.value = $val "
  62. "RETURN n.id",
  63. nid=nid, val=val)
  64. return result.single()[0]
  65. if not self.is_valid_datavalue(value):
  66. return None
  67. node = self._run_and_return(query, nid=str(self.new_id()), val=repr(value))
  68. return UUID(node) if node is not None else None
  69. def create_dict(self, source: Element, value: Any, target: Element) -> Optional[Tuple[Edge, Edge, Node]]:
  70. if not self.is_valid_datavalue(value):
  71. return None
  72. edge_node = self.create_edge(source, target)
  73. val_node = self.create_nodevalue(value)
  74. if edge_node is not None and val_node is not None:
  75. self.create_edge(edge_node, val_node)
  76. def read_root(self) -> Node:
  77. return self.root
  78. def read_value(self, node: Node) -> Optional[Any]:
  79. def query(tx, nid):
  80. result = tx.run("MATCH (n:Node) "
  81. "WHERE n.id = $nid "
  82. "RETURN n.value",
  83. nid=nid)
  84. try:
  85. return result.single()[0]
  86. except TypeError:
  87. # No node found for nid
  88. return None
  89. value = self._run_and_return(query, nid=str(node))
  90. return literal_eval(value) if value is not None else None
  91. def read_outgoing(self, elem: Element) -> Optional[List[Edge]]:
  92. def query(tx, eid):
  93. result = tx.run("MATCH (elem:Element) -[:Source]-> (e:Edge) "
  94. "WHERE elem.id = $eid "
  95. "RETURN e.id",
  96. eid=eid)
  97. return result.value()
  98. source_exists = self._run_and_return(self._existence_check, eid=str(elem)) is not None
  99. if source_exists:
  100. result = self._run_and_return(query, eid=str(elem))
  101. return [UUID(x) for x in result] if result is not None else None
  102. def read_incoming(self, elem: Element) -> Optional[List[Edge]]:
  103. def query(tx, eid):
  104. result = tx.run("MATCH (elem:Element) <-[:Target]- (e:Edge) "
  105. "WHERE elem.id = $eid "
  106. "RETURN e.id",
  107. eid=eid)
  108. return result.value()
  109. target_exists = self._run_and_return(self._existence_check, eid=str(elem)) is not None
  110. if target_exists:
  111. result = self._run_and_return(query, eid=str(elem))
  112. return [UUID(x) for x in result] if result is not None else None
  113. def read_edge(self, edge: Edge) -> Tuple[Optional[Node], Optional[Node]]:
  114. def query(tx, eid):
  115. result = tx.run("MATCH (src) -[:Source]-> (e:Edge) -[:Target]-> (tgt)"
  116. "WHERE e.id = $eid "
  117. "RETURN src.id, tgt.id",
  118. eid=eid)
  119. return result.single()
  120. edge_exists = self._run_and_return(self._existence_check, eid=str(edge), label="Edge") is not None
  121. if edge_exists:
  122. try:
  123. src, tgt = self._run_and_return(query, eid=str(edge))
  124. return UUID(src), UUID(tgt)
  125. except TypeError:
  126. return None, None
  127. else:
  128. return None, None
  129. def read_dict(self, elem: Element, value: Any) -> Optional[Element]:
  130. def query(tx, eid, label_value):
  131. result = tx.run("MATCH (src) -[:Source]-> (e:Edge) -[:Target]-> (tgt), "
  132. "(e) -[:Source]-> (:Edge) -[:Target]-> (label)"
  133. "WHERE src.id = $eid "
  134. "AND label.value = $val "
  135. "RETURN tgt.id",
  136. eid=eid, val=label_value)
  137. try:
  138. return result.single()[0]
  139. except TypeError:
  140. # No edge found with given label
  141. return None
  142. elem_exists = self._run_and_return(self._existence_check, eid=str(elem)) is not None
  143. if elem_exists:
  144. if isinstance(value, UUID):
  145. return None
  146. result = self._run_and_return(query, eid=str(elem), label_value=repr(value))
  147. return UUID(result) if result is not None else None
  148. def read_dict_keys(self, elem: Element) -> Optional[List[Any]]:
  149. def query(tx, eid):
  150. result = tx.run("MATCH (src) -[:Source]-> (e:Edge) -[:Target]-> (), "
  151. "(e) -[:Source]-> (:Edge) -[:Target]-> (label)"
  152. "WHERE src.id = $eid "
  153. "RETURN label.id",
  154. eid=eid)
  155. try:
  156. return result.value()
  157. except TypeError:
  158. # No edge found with given label
  159. return None
  160. elem_exists = self._run_and_return(self._existence_check, eid=str(elem)) is not None
  161. if elem_exists:
  162. result = self._run_and_return(query, eid=str(elem))
  163. return [UUID(x) for x in result if x is not None]
  164. def read_dict_edge(self, elem: Element, value: Any) -> Optional[Edge]:
  165. def query(tx, eid, label_value):
  166. result = tx.run("MATCH (src) -[:Source]-> (e:Edge) -[:Target]-> (), "
  167. "(e) -[:Source]-> (:Edge) -[:Target]-> (label)"
  168. "WHERE src.id = $eid "
  169. "AND label.value = $val "
  170. "RETURN e.id",
  171. eid=eid, val=label_value)
  172. try:
  173. return result.single()[0]
  174. except TypeError:
  175. # No edge found with given label
  176. return None
  177. elem_exists = self._run_and_return(self._existence_check, eid=str(elem)) is not None
  178. if elem_exists:
  179. result = self._run_and_return(query, eid=str(elem), label_value=repr(value))
  180. return UUID(result) if result is not None else None
  181. def read_dict_node(self, elem: Element, value_node: Node) -> Optional[Element]:
  182. def query(tx, eid, label_id):
  183. result = tx.run("MATCH (src) -[:Source]-> (e:Edge) -[:Target]-> (tgt), "
  184. "(e) -[:Source]-> (:Edge) -[:Target]-> (label)"
  185. "WHERE src.id = $eid "
  186. "AND label.id = $lid "
  187. "RETURN tgt.id",
  188. eid=eid, lid=label_id)
  189. try:
  190. return result.single()[0]
  191. except TypeError:
  192. # No edge found with given label
  193. return None
  194. elem_exists = self._run_and_return(self._existence_check, eid=str(elem)) is not None
  195. if elem_exists:
  196. result = self._run_and_return(query, eid=str(elem), label_id=str(value_node))
  197. return UUID(result) if result is not None else None
  198. def read_dict_node_edge(self, elem: Element, value_node: Node) -> Optional[Edge]:
  199. def query(tx, eid, label_id):
  200. result = tx.run("MATCH (src) -[:Source]-> (e:Edge) -[:Target]-> (), "
  201. "(e) -[:Source]-> (:Edge) -[:Target]-> (label)"
  202. "WHERE src.id = $eid "
  203. "AND label.id = $lid "
  204. "RETURN e.id",
  205. eid=eid, lid=label_id)
  206. try:
  207. return result.single()[0]
  208. except TypeError:
  209. # No edge found with given label
  210. return None
  211. elem_exists = self._run_and_return(self._existence_check, eid=str(elem)) is not None
  212. if elem_exists:
  213. result = self._run_and_return(query, eid=str(elem), label_id=str(value_node))
  214. return UUID(result) if result is not None else None
  215. def read_reverse_dict(self, elem: Element, value: Any) -> Optional[List[Element]]:
  216. def query(tx, eid, label_value):
  217. result = tx.run("MATCH (src) -[:Source]-> (e:Edge) -[:Target]-> (tgt), "
  218. "(e) -[:Source]-> (:Edge) -[:Target]-> (label)"
  219. "WHERE tgt.id = $eid "
  220. "AND label.value = $val "
  221. "RETURN src.id",
  222. eid=eid, val=label_value)
  223. try:
  224. return result.value()
  225. except TypeError:
  226. # No edge found with given label
  227. return None
  228. elem_exists = self._run_and_return(self._existence_check, eid=str(elem)) is not None
  229. if elem_exists:
  230. result = self._run_and_return(query, eid=str(elem), label_value=repr(value))
  231. return [UUID(x) for x in result if x is not None]
  232. def delete_node(self, node: Node) -> None:
  233. def query(tx, nid):
  234. result = tx.run("MATCH (n:Node) "
  235. "WHERE n.id = $nid "
  236. "OPTIONAL MATCH (n) -- (e:Edge) "
  237. "DETACH DELETE n "
  238. "RETURN e.id",
  239. nid=nid)
  240. return result.value()
  241. to_be_deleted = self._run_and_return(query, nid=str(node))
  242. to_be_deleted = [UUID(x) for x in to_be_deleted if x is not None]
  243. for edge in to_be_deleted:
  244. self.delete_edge(edge)
  245. def delete_edge(self, edge: Edge) -> None:
  246. def query(tx, eid):
  247. result = tx.run("MATCH (e1:Edge) "
  248. "WHERE e1.id = $eid "
  249. "OPTIONAL MATCH (e1) -- (e2:Edge) "
  250. "WHERE (e1) -[:Source]-> (e2) "
  251. "OR (e1) <-[:Target]- (e2) "
  252. "DETACH DELETE e1 "
  253. "RETURN e2.id",
  254. eid=eid)
  255. return result.value()
  256. to_be_deleted = self._run_and_return(query, eid=str(edge))
  257. to_be_deleted = [UUID(x) for x in to_be_deleted if x is not None]
  258. for edge in to_be_deleted:
  259. self.delete_edge(edge)