|
| 1 | +from typing import Any |
| 2 | + |
| 3 | +from infrahub.core.query import Query, QueryType |
| 4 | +from infrahub.database import InfrahubDatabase |
| 5 | + |
| 6 | +from ..model.path import TrackingId |
| 7 | + |
| 8 | + |
| 9 | +class DiffSummaryCountsEnricherQuery(Query): |
| 10 | + """Update summary counters for a given diff""" |
| 11 | + |
| 12 | + name = "diff_summary_count_enricher" |
| 13 | + type = QueryType.WRITE |
| 14 | + insert_return = False |
| 15 | + |
| 16 | + def __init__( |
| 17 | + self, |
| 18 | + diff_branch_name: str, |
| 19 | + tracking_id: TrackingId | None = None, |
| 20 | + diff_id: str | None = None, |
| 21 | + node_uuids: list[str] | None = None, |
| 22 | + **kwargs: Any, |
| 23 | + ) -> None: |
| 24 | + super().__init__(**kwargs) |
| 25 | + if (diff_id is None and tracking_id is None) or (diff_id and tracking_id): |
| 26 | + raise ValueError("EnrichedDiffAllConflictsQuery requires one and only one of `tracking_id` or `diff_id`") |
| 27 | + self.diff_branch_name = diff_branch_name |
| 28 | + self.tracking_id = tracking_id |
| 29 | + self.diff_id = diff_id |
| 30 | + if self.tracking_id is None and self.diff_id is None: |
| 31 | + raise RuntimeError("tracking_id or diff_id is required") |
| 32 | + self.node_uuids = node_uuids |
| 33 | + |
| 34 | + async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: |
| 35 | + self.params = { |
| 36 | + "diff_branch_name": self.diff_branch_name, |
| 37 | + "diff_id": self.diff_id, |
| 38 | + "tracking_id": self.tracking_id.serialize() if self.tracking_id else None, |
| 39 | + "node_uuids": self.node_uuids, |
| 40 | + } |
| 41 | + query = """ |
| 42 | +MATCH (root:DiffRoot) |
| 43 | +WHERE ($diff_id IS NOT NULL AND root.uuid = $diff_id) |
| 44 | +OR ($tracking_id IS NOT NULL AND root.tracking_id = $tracking_id AND root.diff_branch = $diff_branch_name) |
| 45 | +MATCH (root)-[:DIFF_HAS_NODE]->(dn:DiffNode) |
| 46 | +WHERE $node_uuids IS NULL OR dn.uuid IN $node_uuids |
| 47 | +CALL { |
| 48 | + // ---------------------- |
| 49 | + // handle attribute count updates |
| 50 | + // ---------------------- |
| 51 | + WITH dn |
| 52 | + MATCH (dn)-[:DIFF_HAS_ATTRIBUTE]->(da:DiffAttribute) |
| 53 | + CALL { |
| 54 | + WITH da |
| 55 | + OPTIONAL MATCH (da)-[:DIFF_HAS_PROPERTY]->(dp:DiffProperty)-[:DIFF_HAS_CONFLICT]->(dc:DiffConflict) |
| 56 | + WITH da, count(dc) AS num_conflicts |
| 57 | + SET da.num_conflicts = num_conflicts |
| 58 | + SET da.contains_conflict = (num_conflicts > 0) |
| 59 | + } |
| 60 | + CALL { |
| 61 | + WITH da |
| 62 | + OPTIONAL MATCH (da)-[:DIFF_HAS_PROPERTY]->(dp:DiffProperty {action: "added"}) |
| 63 | + WITH da, count(dp.action) AS num_added |
| 64 | + SET da.num_added = num_added |
| 65 | + } |
| 66 | + CALL { |
| 67 | + WITH da |
| 68 | + OPTIONAL MATCH (da)-[:DIFF_HAS_PROPERTY]->(dp:DiffProperty {action: "updated"}) |
| 69 | + WITH da, count(dp.action) AS num_updated |
| 70 | + SET da.num_updated = num_updated |
| 71 | + } |
| 72 | + CALL { |
| 73 | + WITH da |
| 74 | + OPTIONAL MATCH (da)-[:DIFF_HAS_PROPERTY]->(dp:DiffProperty {action: "removed"}) |
| 75 | + WITH da, count(dp.action) AS num_removed |
| 76 | + SET da.num_removed = num_removed |
| 77 | + } |
| 78 | +} |
| 79 | +CALL { |
| 80 | + WITH dn |
| 81 | + MATCH (dn)-[:DIFF_HAS_RELATIONSHIP]->(dr:DiffRelationship) |
| 82 | + CALL { |
| 83 | + // ---------------------- |
| 84 | + // handle relationship element count updates |
| 85 | + // ---------------------- |
| 86 | + WITH dr |
| 87 | + MATCH (dr)-[:DIFF_HAS_ELEMENT]->(dre:DiffRelationshipElement) |
| 88 | + CALL { |
| 89 | + WITH dre |
| 90 | + OPTIONAL MATCH (dre)-[*..4]->(dc:DiffConflict) |
| 91 | + WITH dre, count(dc) AS num_conflicts |
| 92 | + SET dre.num_conflicts = num_conflicts |
| 93 | + SET dre.contains_conflict = (num_conflicts > 0) |
| 94 | + } |
| 95 | + CALL { |
| 96 | + WITH dre |
| 97 | + OPTIONAL MATCH (dre)-[:DIFF_HAS_PROPERTY]->(dp:DiffProperty {action: "added"}) |
| 98 | + WITH dre, count(dp.action) AS num_added |
| 99 | + SET dre.num_added = num_added |
| 100 | + } |
| 101 | + CALL { |
| 102 | + WITH dre |
| 103 | + OPTIONAL MATCH (dre)-[:DIFF_HAS_PROPERTY]->(dp:DiffProperty {action: "updated"}) |
| 104 | + WITH dre, count(dp.action) AS num_updated |
| 105 | + SET dre.num_updated = num_updated |
| 106 | + } |
| 107 | + CALL { |
| 108 | + WITH dre |
| 109 | + OPTIONAL MATCH (dre)-[:DIFF_HAS_PROPERTY]->(dp:DiffProperty {action: "removed"}) |
| 110 | + WITH dre, count(dp.action) AS num_removed |
| 111 | + SET dre.num_removed = num_removed |
| 112 | + } |
| 113 | + } |
| 114 | + // ---------------------- |
| 115 | + // handle relationship count updates |
| 116 | + // ---------------------- |
| 117 | + OPTIONAL MATCH (dr)-[:DIFF_HAS_ELEMENT]->(conflict_dre:DiffRelationshipElement {contains_conflict: TRUE}) |
| 118 | + WITH dr, sum(conflict_dre.num_conflicts) AS num_conflicts |
| 119 | + SET dr.num_conflicts = num_conflicts |
| 120 | + SET dr.contains_conflict = (num_conflicts > 0) |
| 121 | + WITH dr |
| 122 | + CALL { |
| 123 | + WITH dr |
| 124 | + OPTIONAL MATCH (dr)-[:DIFF_HAS_ELEMENT]->(dre:DiffRelationshipElement {action: "added"}) |
| 125 | + WITH dr, count(dre.action) AS num_added |
| 126 | + SET dr.num_added = num_added |
| 127 | + } |
| 128 | + CALL { |
| 129 | + WITH dr |
| 130 | + OPTIONAL MATCH (dr)-[:DIFF_HAS_ELEMENT]->(dre:DiffRelationshipElement {action: "updated"}) |
| 131 | + WITH dr, count(dre.action) AS num_updated |
| 132 | + SET dr.num_updated = num_updated |
| 133 | + } |
| 134 | + CALL { |
| 135 | + WITH dr |
| 136 | + OPTIONAL MATCH (dr)-[:DIFF_HAS_ELEMENT]->(dre:DiffRelationshipElement {action: "removed"}) |
| 137 | + WITH dr, count(dre.action) AS num_removed |
| 138 | + SET dr.num_removed = num_removed |
| 139 | + } |
| 140 | +} |
| 141 | +// ---------------------- |
| 142 | +// handle node count updates |
| 143 | +// ---------------------- |
| 144 | +WITH root, dn, coalesce(dn.num_conflicts, 0) AS previous_num_conflicts |
| 145 | +CALL { |
| 146 | + // ---------------------- |
| 147 | + // handle node num_conflicts update |
| 148 | + // ---------------------- |
| 149 | + WITH dn |
| 150 | + OPTIONAL MATCH (dn)-[:DIFF_HAS_ATTRIBUTE]->(da:DiffAttribute {contains_conflict: TRUE}) |
| 151 | + RETURN sum(da.num_conflicts) AS num_conflicts |
| 152 | + UNION ALL |
| 153 | + WITH dn |
| 154 | + OPTIONAL MATCH (dn)-[:DIFF_HAS_RELATIONSHIP]->(dr:DiffRelationship {contains_conflict: TRUE}) |
| 155 | + RETURN sum(dr.num_conflicts) AS num_conflicts |
| 156 | + UNION ALL |
| 157 | + WITH dn |
| 158 | + OPTIONAL MATCH (dn)-[:DIFF_HAS_CONFLICT]->(dc:DiffConflict) |
| 159 | + RETURN count(dc) AS num_conflicts |
| 160 | +} |
| 161 | +WITH root, dn, previous_num_conflicts, sum(num_conflicts) AS updated_num_conflicts |
| 162 | +SET dn.num_conflicts = updated_num_conflicts |
| 163 | +SET dn.contains_conflict = (updated_num_conflicts > 0) |
| 164 | +WITH root, dn, updated_num_conflicts - previous_num_conflicts AS num_conflicts_delta |
| 165 | +CALL { |
| 166 | + // ---------------------- |
| 167 | + // handle node added/updated/removed updates |
| 168 | + // ---------------------- |
| 169 | + WITH dn |
| 170 | + OPTIONAL MATCH (dn)-[:DIFF_HAS_ATTRIBUTE]->(da:DiffAttribute) |
| 171 | + WITH dn, collect(da.action) AS attr_actions |
| 172 | + OPTIONAL MATCH (dn)-[:DIFF_HAS_RELATIONSHIP]->(dr:DiffRelationship) |
| 173 | + WITH dn, attr_actions, collect(dr.action) AS rel_actions |
| 174 | + WITH dn, attr_actions + rel_actions AS actions |
| 175 | + WITH dn, reduce(counts = [0,0,0], a IN actions | |
| 176 | + CASE |
| 177 | + WHEN a = "added" THEN [counts[0] + 1, counts[1], counts[2]] |
| 178 | + WHEN a = "updated" THEN [counts[0], counts[1] + 1, counts[2]] |
| 179 | + WHEN a = "removed" THEN [counts[0], counts[1], counts[2] + 1] |
| 180 | + ELSE counts |
| 181 | + END |
| 182 | + ) AS action_counts |
| 183 | + WITH dn, action_counts[0] AS num_added, action_counts[1] AS num_updated, action_counts[2] AS num_removed |
| 184 | + SET dn.num_added = num_added |
| 185 | + SET dn.num_updated = num_updated |
| 186 | + SET dn.num_removed = num_removed |
| 187 | +} |
| 188 | +// ---------------------- |
| 189 | +// handle conflict updates for parent nodes |
| 190 | +// ---------------------- |
| 191 | +WITH root, dn, num_conflicts_delta |
| 192 | +CALL { |
| 193 | + WITH dn, num_conflicts_delta |
| 194 | + OPTIONAL MATCH (dn)-[:DIFF_HAS_RELATIONSHIP|DIFF_HAS_NODE*1..]->(parent_node:DiffNode) |
| 195 | + SET parent_node.num_conflicts = parent_node.num_conflicts + num_conflicts_delta |
| 196 | + SET parent_node.contains_conflict = (parent_node.num_conflicts > 0) |
| 197 | +} |
| 198 | +// ---------------------- |
| 199 | +// handle root count updates |
| 200 | +// ---------------------- |
| 201 | +WITH root, sum(num_conflicts_delta) AS total_conflicts_delta |
| 202 | +CALL { |
| 203 | + WITH root, total_conflicts_delta |
| 204 | + SET root.num_conflicts = coalesce(root.num_conflicts, 0) + total_conflicts_delta |
| 205 | + SET root.contains_conflict = root.num_conflicts > 0 |
| 206 | + WITH root |
| 207 | + OPTIONAL MATCH (root)-[:DIFF_HAS_NODE]->(dn:DiffNode {action: "added"}) |
| 208 | + WITH root, count(dn.action) AS num_added |
| 209 | + SET root.num_added = num_added |
| 210 | + WITH root |
| 211 | + OPTIONAL MATCH (root)-[:DIFF_HAS_NODE]->(dn:DiffNode {action: "updated"}) |
| 212 | + WITH root, count(dn.action) AS num_updated |
| 213 | + SET root.num_updated = num_updated |
| 214 | + WITH root |
| 215 | + OPTIONAL MATCH (root)-[:DIFF_HAS_NODE]->(dn:DiffNode {action: "removed"}) |
| 216 | + WITH root, count(dn.action) AS num_removed |
| 217 | + SET root.num_removed = num_removed |
| 218 | +} |
| 219 | + """ |
| 220 | + self.add_to_query(query) |
0 commit comments