图片解析应用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

573 lines
17 KiB

  1. import sys
  2. from collections import OrderedDict
  3. from distutils.util import strtobool
  4. # from prettytable import PrettyTable
  5. from redis import ResponseError
  6. from .edge import Edge
  7. from .exceptions import VersionMismatchException
  8. from .node import Node
  9. from .path import Path
  10. LABELS_ADDED = "Labels added"
  11. LABELS_REMOVED = "Labels removed"
  12. NODES_CREATED = "Nodes created"
  13. NODES_DELETED = "Nodes deleted"
  14. RELATIONSHIPS_DELETED = "Relationships deleted"
  15. PROPERTIES_SET = "Properties set"
  16. PROPERTIES_REMOVED = "Properties removed"
  17. RELATIONSHIPS_CREATED = "Relationships created"
  18. INDICES_CREATED = "Indices created"
  19. INDICES_DELETED = "Indices deleted"
  20. CACHED_EXECUTION = "Cached execution"
  21. INTERNAL_EXECUTION_TIME = "internal execution time"
  22. STATS = [
  23. LABELS_ADDED,
  24. LABELS_REMOVED,
  25. NODES_CREATED,
  26. PROPERTIES_SET,
  27. PROPERTIES_REMOVED,
  28. RELATIONSHIPS_CREATED,
  29. NODES_DELETED,
  30. RELATIONSHIPS_DELETED,
  31. INDICES_CREATED,
  32. INDICES_DELETED,
  33. CACHED_EXECUTION,
  34. INTERNAL_EXECUTION_TIME,
  35. ]
  36. class ResultSetColumnTypes:
  37. COLUMN_UNKNOWN = 0
  38. COLUMN_SCALAR = 1
  39. COLUMN_NODE = 2 # Unused as of RedisGraph v2.1.0, retained for backwards compatibility. # noqa
  40. COLUMN_RELATION = 3 # Unused as of RedisGraph v2.1.0, retained for backwards compatibility. # noqa
  41. class ResultSetScalarTypes:
  42. VALUE_UNKNOWN = 0
  43. VALUE_NULL = 1
  44. VALUE_STRING = 2
  45. VALUE_INTEGER = 3
  46. VALUE_BOOLEAN = 4
  47. VALUE_DOUBLE = 5
  48. VALUE_ARRAY = 6
  49. VALUE_EDGE = 7
  50. VALUE_NODE = 8
  51. VALUE_PATH = 9
  52. VALUE_MAP = 10
  53. VALUE_POINT = 11
  54. class QueryResult:
  55. def __init__(self, graph, response, profile=False):
  56. """
  57. A class that represents a result of the query operation.
  58. Args:
  59. graph:
  60. The graph on which the query was executed.
  61. response:
  62. The response from the server.
  63. profile:
  64. A boolean indicating if the query command was "GRAPH.PROFILE"
  65. """
  66. self.graph = graph
  67. self.header = []
  68. self.result_set = []
  69. # in case of an error an exception will be raised
  70. self._check_for_errors(response)
  71. if len(response) == 1:
  72. self.parse_statistics(response[0])
  73. elif profile:
  74. self.parse_profile(response)
  75. else:
  76. # start by parsing statistics, matches the one we have
  77. self.parse_statistics(response[-1]) # Last element.
  78. self.parse_results(response)
  79. def _check_for_errors(self, response):
  80. """
  81. Check if the response contains an error.
  82. """
  83. if isinstance(response[0], ResponseError):
  84. error = response[0]
  85. if str(error) == "version mismatch":
  86. version = response[1]
  87. error = VersionMismatchException(version)
  88. raise error
  89. # If we encountered a run-time error, the last response
  90. # element will be an exception
  91. if isinstance(response[-1], ResponseError):
  92. raise response[-1]
  93. def parse_results(self, raw_result_set):
  94. """
  95. Parse the query execution result returned from the server.
  96. """
  97. self.header = self.parse_header(raw_result_set)
  98. # Empty header.
  99. if len(self.header) == 0:
  100. return
  101. self.result_set = self.parse_records(raw_result_set)
  102. def parse_statistics(self, raw_statistics):
  103. """
  104. Parse the statistics returned in the response.
  105. """
  106. self.statistics = {}
  107. # decode statistics
  108. for idx, stat in enumerate(raw_statistics):
  109. if isinstance(stat, bytes):
  110. raw_statistics[idx] = stat.decode()
  111. for s in STATS:
  112. v = self._get_value(s, raw_statistics)
  113. if v is not None:
  114. self.statistics[s] = v
  115. def parse_header(self, raw_result_set):
  116. """
  117. Parse the header of the result.
  118. """
  119. # An array of column name/column type pairs.
  120. header = raw_result_set[0]
  121. return header
  122. def parse_records(self, raw_result_set):
  123. """
  124. Parses the result set and returns a list of records.
  125. """
  126. records = [
  127. [
  128. self.parse_record_types[self.header[idx][0]](cell)
  129. for idx, cell in enumerate(row)
  130. ]
  131. for row in raw_result_set[1]
  132. ]
  133. return records
  134. def parse_entity_properties(self, props):
  135. """
  136. Parse node / edge properties.
  137. """
  138. # [[name, value type, value] X N]
  139. properties = {}
  140. for prop in props:
  141. prop_name = self.graph.get_property(prop[0])
  142. prop_value = self.parse_scalar(prop[1:])
  143. properties[prop_name] = prop_value
  144. return properties
  145. def parse_string(self, cell):
  146. """
  147. Parse the cell as a string.
  148. """
  149. if isinstance(cell, bytes):
  150. return cell.decode()
  151. elif not isinstance(cell, str):
  152. return str(cell)
  153. else:
  154. return cell
  155. def parse_node(self, cell):
  156. """
  157. Parse the cell to a node.
  158. """
  159. # Node ID (integer),
  160. # [label string offset (integer)],
  161. # [[name, value type, value] X N]
  162. node_id = int(cell[0])
  163. labels = None
  164. if len(cell[1]) > 0:
  165. labels = []
  166. for inner_label in cell[1]:
  167. labels.append(self.graph.get_label(inner_label))
  168. properties = self.parse_entity_properties(cell[2])
  169. return Node(node_id=node_id, label=labels, properties=properties)
  170. def parse_edge(self, cell):
  171. """
  172. Parse the cell to an edge.
  173. """
  174. # Edge ID (integer),
  175. # reltype string offset (integer),
  176. # src node ID offset (integer),
  177. # dest node ID offset (integer),
  178. # [[name, value, value type] X N]
  179. edge_id = int(cell[0])
  180. relation = self.graph.get_relation(cell[1])
  181. src_node_id = int(cell[2])
  182. dest_node_id = int(cell[3])
  183. properties = self.parse_entity_properties(cell[4])
  184. return Edge(
  185. src_node_id, relation, dest_node_id, edge_id=edge_id, properties=properties
  186. )
  187. def parse_path(self, cell):
  188. """
  189. Parse the cell to a path.
  190. """
  191. nodes = self.parse_scalar(cell[0])
  192. edges = self.parse_scalar(cell[1])
  193. return Path(nodes, edges)
  194. def parse_map(self, cell):
  195. """
  196. Parse the cell as a map.
  197. """
  198. m = OrderedDict()
  199. n_entries = len(cell)
  200. # A map is an array of key value pairs.
  201. # 1. key (string)
  202. # 2. array: (value type, value)
  203. for i in range(0, n_entries, 2):
  204. key = self.parse_string(cell[i])
  205. m[key] = self.parse_scalar(cell[i + 1])
  206. return m
  207. def parse_point(self, cell):
  208. """
  209. Parse the cell to point.
  210. """
  211. p = {}
  212. # A point is received an array of the form: [latitude, longitude]
  213. # It is returned as a map of the form: {"latitude": latitude, "longitude": longitude} # noqa
  214. p["latitude"] = float(cell[0])
  215. p["longitude"] = float(cell[1])
  216. return p
  217. def parse_null(self, cell):
  218. """
  219. Parse a null value.
  220. """
  221. return None
  222. def parse_integer(self, cell):
  223. """
  224. Parse the integer value from the cell.
  225. """
  226. return int(cell)
  227. def parse_boolean(self, value):
  228. """
  229. Parse the cell value as a boolean.
  230. """
  231. value = value.decode() if isinstance(value, bytes) else value
  232. try:
  233. scalar = True if strtobool(value) else False
  234. except ValueError:
  235. sys.stderr.write("unknown boolean type\n")
  236. scalar = None
  237. return scalar
  238. def parse_double(self, cell):
  239. """
  240. Parse the cell as a double.
  241. """
  242. return float(cell)
  243. def parse_array(self, value):
  244. """
  245. Parse an array of values.
  246. """
  247. scalar = [self.parse_scalar(value[i]) for i in range(len(value))]
  248. return scalar
  249. def parse_unknown(self, cell):
  250. """
  251. Parse a cell of unknown type.
  252. """
  253. sys.stderr.write("Unknown type\n")
  254. return None
  255. def parse_scalar(self, cell):
  256. """
  257. Parse a scalar value from a cell in the result set.
  258. """
  259. scalar_type = int(cell[0])
  260. value = cell[1]
  261. scalar = self.parse_scalar_types[scalar_type](value)
  262. return scalar
  263. def parse_profile(self, response):
  264. self.result_set = [x[0 : x.index(",")].strip() for x in response]
  265. def is_empty(self):
  266. return len(self.result_set) == 0
  267. @staticmethod
  268. def _get_value(prop, statistics):
  269. for stat in statistics:
  270. if prop in stat:
  271. return float(stat.split(": ")[1].split(" ")[0])
  272. return None
  273. def _get_stat(self, stat):
  274. return self.statistics[stat] if stat in self.statistics else 0
  275. @property
  276. def labels_added(self):
  277. """Returns the number of labels added in the query"""
  278. return self._get_stat(LABELS_ADDED)
  279. @property
  280. def labels_removed(self):
  281. """Returns the number of labels removed in the query"""
  282. return self._get_stat(LABELS_REMOVED)
  283. @property
  284. def nodes_created(self):
  285. """Returns the number of nodes created in the query"""
  286. return self._get_stat(NODES_CREATED)
  287. @property
  288. def nodes_deleted(self):
  289. """Returns the number of nodes deleted in the query"""
  290. return self._get_stat(NODES_DELETED)
  291. @property
  292. def properties_set(self):
  293. """Returns the number of properties set in the query"""
  294. return self._get_stat(PROPERTIES_SET)
  295. @property
  296. def properties_removed(self):
  297. """Returns the number of properties removed in the query"""
  298. return self._get_stat(PROPERTIES_REMOVED)
  299. @property
  300. def relationships_created(self):
  301. """Returns the number of relationships created in the query"""
  302. return self._get_stat(RELATIONSHIPS_CREATED)
  303. @property
  304. def relationships_deleted(self):
  305. """Returns the number of relationships deleted in the query"""
  306. return self._get_stat(RELATIONSHIPS_DELETED)
  307. @property
  308. def indices_created(self):
  309. """Returns the number of indices created in the query"""
  310. return self._get_stat(INDICES_CREATED)
  311. @property
  312. def indices_deleted(self):
  313. """Returns the number of indices deleted in the query"""
  314. return self._get_stat(INDICES_DELETED)
  315. @property
  316. def cached_execution(self):
  317. """Returns whether or not the query execution plan was cached"""
  318. return self._get_stat(CACHED_EXECUTION) == 1
  319. @property
  320. def run_time_ms(self):
  321. """Returns the server execution time of the query"""
  322. return self._get_stat(INTERNAL_EXECUTION_TIME)
  323. @property
  324. def parse_scalar_types(self):
  325. return {
  326. ResultSetScalarTypes.VALUE_NULL: self.parse_null,
  327. ResultSetScalarTypes.VALUE_STRING: self.parse_string,
  328. ResultSetScalarTypes.VALUE_INTEGER: self.parse_integer,
  329. ResultSetScalarTypes.VALUE_BOOLEAN: self.parse_boolean,
  330. ResultSetScalarTypes.VALUE_DOUBLE: self.parse_double,
  331. ResultSetScalarTypes.VALUE_ARRAY: self.parse_array,
  332. ResultSetScalarTypes.VALUE_NODE: self.parse_node,
  333. ResultSetScalarTypes.VALUE_EDGE: self.parse_edge,
  334. ResultSetScalarTypes.VALUE_PATH: self.parse_path,
  335. ResultSetScalarTypes.VALUE_MAP: self.parse_map,
  336. ResultSetScalarTypes.VALUE_POINT: self.parse_point,
  337. ResultSetScalarTypes.VALUE_UNKNOWN: self.parse_unknown,
  338. }
  339. @property
  340. def parse_record_types(self):
  341. return {
  342. ResultSetColumnTypes.COLUMN_SCALAR: self.parse_scalar,
  343. ResultSetColumnTypes.COLUMN_NODE: self.parse_node,
  344. ResultSetColumnTypes.COLUMN_RELATION: self.parse_edge,
  345. ResultSetColumnTypes.COLUMN_UNKNOWN: self.parse_unknown,
  346. }
  347. class AsyncQueryResult(QueryResult):
  348. """
  349. Async version for the QueryResult class - a class that
  350. represents a result of the query operation.
  351. """
  352. def __init__(self):
  353. """
  354. To init the class you must call self.initialize()
  355. """
  356. pass
  357. async def initialize(self, graph, response, profile=False):
  358. """
  359. Initializes the class.
  360. Args:
  361. graph:
  362. The graph on which the query was executed.
  363. response:
  364. The response from the server.
  365. profile:
  366. A boolean indicating if the query command was "GRAPH.PROFILE"
  367. """
  368. self.graph = graph
  369. self.header = []
  370. self.result_set = []
  371. # in case of an error an exception will be raised
  372. self._check_for_errors(response)
  373. if len(response) == 1:
  374. self.parse_statistics(response[0])
  375. elif profile:
  376. self.parse_profile(response)
  377. else:
  378. # start by parsing statistics, matches the one we have
  379. self.parse_statistics(response[-1]) # Last element.
  380. await self.parse_results(response)
  381. return self
  382. async def parse_node(self, cell):
  383. """
  384. Parses a node from the cell.
  385. """
  386. # Node ID (integer),
  387. # [label string offset (integer)],
  388. # [[name, value type, value] X N]
  389. labels = None
  390. if len(cell[1]) > 0:
  391. labels = []
  392. for inner_label in cell[1]:
  393. labels.append(await self.graph.get_label(inner_label))
  394. properties = await self.parse_entity_properties(cell[2])
  395. node_id = int(cell[0])
  396. return Node(node_id=node_id, label=labels, properties=properties)
  397. async def parse_scalar(self, cell):
  398. """
  399. Parses a scalar value from the server response.
  400. """
  401. scalar_type = int(cell[0])
  402. value = cell[1]
  403. try:
  404. scalar = await self.parse_scalar_types[scalar_type](value)
  405. except TypeError:
  406. # Not all of the functions are async
  407. scalar = self.parse_scalar_types[scalar_type](value)
  408. return scalar
  409. async def parse_records(self, raw_result_set):
  410. """
  411. Parses the result set and returns a list of records.
  412. """
  413. records = []
  414. for row in raw_result_set[1]:
  415. record = [
  416. await self.parse_record_types[self.header[idx][0]](cell)
  417. for idx, cell in enumerate(row)
  418. ]
  419. records.append(record)
  420. return records
  421. async def parse_results(self, raw_result_set):
  422. """
  423. Parse the query execution result returned from the server.
  424. """
  425. self.header = self.parse_header(raw_result_set)
  426. # Empty header.
  427. if len(self.header) == 0:
  428. return
  429. self.result_set = await self.parse_records(raw_result_set)
  430. async def parse_entity_properties(self, props):
  431. """
  432. Parse node / edge properties.
  433. """
  434. # [[name, value type, value] X N]
  435. properties = {}
  436. for prop in props:
  437. prop_name = await self.graph.get_property(prop[0])
  438. prop_value = await self.parse_scalar(prop[1:])
  439. properties[prop_name] = prop_value
  440. return properties
  441. async def parse_edge(self, cell):
  442. """
  443. Parse the cell to an edge.
  444. """
  445. # Edge ID (integer),
  446. # reltype string offset (integer),
  447. # src node ID offset (integer),
  448. # dest node ID offset (integer),
  449. # [[name, value, value type] X N]
  450. edge_id = int(cell[0])
  451. relation = await self.graph.get_relation(cell[1])
  452. src_node_id = int(cell[2])
  453. dest_node_id = int(cell[3])
  454. properties = await self.parse_entity_properties(cell[4])
  455. return Edge(
  456. src_node_id, relation, dest_node_id, edge_id=edge_id, properties=properties
  457. )
  458. async def parse_path(self, cell):
  459. """
  460. Parse the cell to a path.
  461. """
  462. nodes = await self.parse_scalar(cell[0])
  463. edges = await self.parse_scalar(cell[1])
  464. return Path(nodes, edges)
  465. async def parse_map(self, cell):
  466. """
  467. Parse the cell to a map.
  468. """
  469. m = OrderedDict()
  470. n_entries = len(cell)
  471. # A map is an array of key value pairs.
  472. # 1. key (string)
  473. # 2. array: (value type, value)
  474. for i in range(0, n_entries, 2):
  475. key = self.parse_string(cell[i])
  476. m[key] = await self.parse_scalar(cell[i + 1])
  477. return m
  478. async def parse_array(self, value):
  479. """
  480. Parse array value.
  481. """
  482. scalar = [await self.parse_scalar(value[i]) for i in range(len(value))]
  483. return scalar