def __resolve_count_descendants(self) -> None: visited: Set[str] = set() def count_successors_by(node_id: str, edge_type: str, path: List[str]) -> Dict[str, int]: result: Dict[str, int] = {} to_visit = list(self.successors(node_id, edge_type)) while to_visit: visit_next: List[str] = [] for elem_id in to_visit: if elem_id not in visited: visited.add(elem_id) elem = self.nodes[elem_id] if not value_in_path_get(elem, NodePath.is_phantom, False): extracted = value_in_path(elem, path) if isinstance(extracted, str): result[extracted] = result.get(extracted, 0) + 1 # check if there is already a successor summary: stop the traversal and take the result. existing = value_in_path(elem, NodePath.descendant_summary) if existing and isinstance(existing, dict): for summary_item, count in existing.items(): result[summary_item] = result.get(summary_item, 0) + count else: visit_next.extend(a for a in self.successors(elem_id, edge_type) if a not in visited) to_visit = visit_next return result for on_kind, prop in GraphResolver.count_successors.items(): for node_id, node in self.g.nodes(data=True): kinds = node.get("kinds_set") if kinds and on_kind in kinds: summary = count_successors_by(node_id, EdgeType.default, prop.extract_path) set_value_in_path(summary, prop.to_path, node) total = reduce(lambda l, r: l + r, summary.values(), 0) set_value_in_path(total, NodePath.descendant_count, node)
def test_set_value_in_path() -> None: js = {"foo": {"bla": {"test": 123}}} res = set_value_in_path(124, ["foo", "bla", "test"], deepcopy(js)) assert res == {"foo": {"bla": {"test": 124}}} res = set_value_in_path(124, ["foo", "bla", "blubber"], deepcopy(js)) assert res == {"foo": {"bla": {"test": 123, "blubber": 124}}} res = set_value_in_path(js, ["foo", "bla", "test"]) assert res == {"foo": {"bla": {"test": js}}} res = {"a": 1} set_value_in_path(23, ["reported"], res) assert res == {"a": 1, "reported": 23}
def with_ancestor(ancestor: Json, prop: ResolveProp) -> None: extracted = value_in_path(ancestor, prop.extract_path) if extracted: set_value_in_path(extracted, prop.to_path, node)
def merge( cursor: str, merge_queries: List[MergeQuery] ) -> Tuple[str, str]: # cursor, query result_cursor = next_crs("merge_result") merge_cursor = next_crs() merge_result = f"LET {result_cursor} = (FOR {merge_cursor} in {cursor} " merge_parts: Json = {} def add_merge_query(mq: MergeQuery, part_result: str) -> None: nonlocal merge_result # make sure the sub query is valid f = mq.query.parts[-1] assert (f.term == AllTerm() and not f.sort and not f.limit and not f.with_clause and not f.tag), "Merge query needs to start with navigation!" merge_crsr = next_crs("merge_part") # make sure the limit only yields one element mg_crs, mg_query = query_string(db, mq.query, query_model, merge_cursor, with_edges, bind_vars, counters, merge_crsr) if mq.only_first: merge_result += ( f"LET {part_result}=FIRST({mg_query} FOR r in {mg_crs} LIMIT 1 RETURN UNSET(r, {unset_props}))" ) else: merge_result += ( f"LET {part_result}=({mg_query} FOR r in {mg_crs} RETURN DISTINCT UNSET(r, {unset_props}))" ) # check if this query points to an already resolved value # Currently only resolved ancestors are taken into account: # <-[1:]- is(cloud|account|region|zone) # noinspection PyUnresolvedReferences def is_already_resolved(q: Query) -> Optional[str]: def check_is(t: IsTerm) -> Optional[str]: for kind in t.kinds: if kind in GraphResolver.resolved_ancestors: return kind return None # noinspection PyTypeChecker return (check_is(q.parts[0].term) if (len(q.parts) == 2 and not q.aggregate and q.parts[1].navigation and q.parts[1].navigation.direction == "in" and q.parts[1].navigation.until > 1 and isinstance(q.parts[0].term, IsTerm)) else None) for mq_in in merge_queries: part_res = next_crs("part_res") resolved = is_already_resolved(mq_in.query) if resolved: merge_result += f'LET {part_res} = DOCUMENT("{db.vertex_name}", {merge_cursor}.refs.{resolved}_id)' else: add_merge_query(mq_in, part_res) set_value_in_path(part_res, mq_in.name, merge_parts) def merge_part_result(d: Json) -> str: vals = [ f"{k}: {merge_part_result(v)}" if isinstance(v, dict) else f"{k}: {v}" for k, v in d.items() ] return "{" + ", ".join(vals) + "}" final_merge = f"RETURN MERGE_RECURSIVE({merge_cursor}, {merge_part_result(merge_parts)}))" return result_cursor, f"{merge_result} {final_merge}"