|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +from typing import TYPE_CHECKING, Any, Sequence |
| 4 | + |
| 5 | +from infrahub.core.migrations.shared import GraphMigration, MigrationResult |
| 6 | +from infrahub.log import get_logger |
| 7 | + |
| 8 | +from ...query import Query, QueryType |
| 9 | + |
| 10 | +if TYPE_CHECKING: |
| 11 | + from infrahub.database import InfrahubDatabase |
| 12 | + |
| 13 | +log = get_logger() |
| 14 | + |
| 15 | + |
| 16 | +class DeletePosthumousEdges(Query): |
| 17 | + name = "delete_posthumous_edges_query" |
| 18 | + type = QueryType.WRITE |
| 19 | + insert_return = False |
| 20 | + |
| 21 | + async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002 |
| 22 | + query = """ |
| 23 | +// ------------ |
| 24 | +// find deleted nodes |
| 25 | +// ------------ |
| 26 | +MATCH (n:Node)-[e:IS_PART_OF]->(:Root) |
| 27 | +WHERE e.status = "deleted" OR e.to IS NOT NULL |
| 28 | +WITH DISTINCT n, e.branch AS delete_branch, e.branch_level AS delete_branch_level, CASE |
| 29 | + WHEN e.status = "deleted" THEN e.from |
| 30 | + ELSE e.to |
| 31 | +END AS delete_time |
| 32 | +// ------------ |
| 33 | +// find the edges added to the deleted node after the delete time |
| 34 | +// ------------ |
| 35 | +MATCH (n)-[added_e]-(peer) |
| 36 | +WHERE added_e.from > delete_time |
| 37 | +AND type(added_e) <> "IS_PART_OF" |
| 38 | +// if the node was deleted on a branch (delete_branch_level > 1), and then updated on main/global (added_e.branch_level = 1), we can ignore it |
| 39 | +AND added_e.branch_level >= delete_branch_level |
| 40 | +AND (added_e.branch = delete_branch OR delete_branch_level = 1) |
| 41 | +WITH DISTINCT n, delete_branch, delete_time, added_e, peer |
| 42 | +// ------------ |
| 43 | +// get the branched_from for the branch on which the node was deleted |
| 44 | +// ------------ |
| 45 | +CALL (added_e) { |
| 46 | + MATCH (b:Branch {name: added_e.branch}) |
| 47 | + RETURN b.branched_from AS added_e_branched_from |
| 48 | +} |
| 49 | +// ------------ |
| 50 | +// account for the following situations, given that the edge update time is after the node delete time |
| 51 | +// - deleted on main/global, updated on branch |
| 52 | +// - illegal if the delete is before branch.branched_from |
| 53 | +// - deleted on branch, updated on branch |
| 54 | +// - illegal |
| 55 | +// ------------ |
| 56 | +WITH n, delete_branch, delete_time, added_e, peer |
| 57 | +WHERE delete_branch = added_e.branch |
| 58 | +OR delete_time < added_e_branched_from |
| 59 | +DELETE added_e |
| 60 | +// -------------- |
| 61 | +// the peer _should_ only be an Attribute, but I want to make sure we don't |
| 62 | +// inadvertently delete Root or an AttributeValue or a Boolean |
| 63 | +// -------------- |
| 64 | +WITH peer |
| 65 | +WHERE "Attribute" IN labels(peer) |
| 66 | +DETACH DELETE peer |
| 67 | + """ |
| 68 | + self.add_to_query(query) |
| 69 | + |
| 70 | + |
| 71 | +class Migration030(GraphMigration): |
| 72 | + """ |
| 73 | + Edges could have been added to Nodes after the Node was deleted, so we need to hard-delete those illegal edges |
| 74 | + """ |
| 75 | + |
| 76 | + name: str = "030_delete_illegal_edges" |
| 77 | + minimum_version: int = 29 |
| 78 | + queries: Sequence[type[Query]] = [DeletePosthumousEdges] |
| 79 | + |
| 80 | + async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002 |
| 81 | + result = MigrationResult() |
| 82 | + return result |
0 commit comments