77
88from infrahub_sdk .uuidt import UUIDT
99
10+ from infrahub .core .changelog .models import (
11+ ChangelogRelationshipMapper ,
12+ RelationshipCardinalityManyChangelog ,
13+ RelationshipCardinalityOneChangelog ,
14+ )
1015from infrahub .core .constants import RelationshipDirection , RelationshipStatus
1116from infrahub .core .constants .database import DatabaseEdgeType
1217from infrahub .core .query import Query , QueryType
1318from infrahub .core .query .subquery import build_subquery_filter , build_subquery_order
1419from infrahub .core .timestamp import Timestamp
1520from infrahub .core .utils import extract_field_filters
21+ from infrahub .log import get_logger
1622
1723if TYPE_CHECKING :
1824 from uuid import UUID
2228 from infrahub .core .branch import Branch
2329 from infrahub .core .node import Node
2430 from infrahub .core .relationship import Relationship
25- from infrahub .core .schema import RelationshipSchema
31+ from infrahub .core .schema import NodeSchema , RelationshipSchema
2632 from infrahub .database import InfrahubDatabase
2733
2834# pylint: disable=redefined-builtin,too-many-lines
2935
36+ log = get_logger ()
37+
3038
3139@dataclass
3240class RelData :
@@ -959,6 +967,8 @@ class RelationshipDeleteAllQuery(Query):
959967 - Set `to` time if an active edge exist on the same branch.
960968 - Create `deleted` edge.
961969 - Apply above to every edges linked to any connected Relationship node.
970+ This query returns node uuids/kinds and corresponding relationship identifiers of deleted nodes,
971+ that are later used to update node changelog.
962972 """
963973
964974 name = "node_delete_all_relationships"
@@ -969,7 +979,7 @@ def __init__(self, node_id: str, **kwargs):
969979 self .node_id = node_id
970980 super ().__init__ (** kwargs )
971981
972- async def query_init (self , db : InfrahubDatabase , ** kwargs ) -> None :
982+ async def query_init (self , db : InfrahubDatabase , ** kwargs ) -> None : # noqa: ARG002
973983 self .params ["source_id" ] = kwargs ["node_id" ]
974984 self .params ["branch" ] = self .branch .name
975985
@@ -987,30 +997,29 @@ async def query_init(self, db: InfrahubDatabase, **kwargs) -> None:
987997 )
988998 self .params .update (rel_params )
989999
990- query_match_relationships = """
1000+ query = """
9911001 MATCH (s:Node { uuid: $source_id })-[active_edge:IS_RELATED]-(rl:Relationship)
9921002 WHERE %(active_rel_filter)s AND active_edge.status = "active"
9931003 WITH DISTINCT rl
9941004 """ % {"active_rel_filter" : active_rel_filter }
9951005
996- self .add_to_query (query_match_relationships )
1006+ edge_types = [
1007+ DatabaseEdgeType .IS_VISIBLE .value ,
1008+ DatabaseEdgeType .IS_PROTECTED .value ,
1009+ DatabaseEdgeType .HAS_OWNER .value ,
1010+ DatabaseEdgeType .HAS_SOURCE .value ,
1011+ ]
9971012
9981013 for arrow_left , arrow_right in (("<-" , "-" ), ("-" , "->" )):
999- for edge_type in [
1000- DatabaseEdgeType .IS_RELATED .value ,
1001- DatabaseEdgeType .IS_VISIBLE .value ,
1002- DatabaseEdgeType .IS_PROTECTED .value ,
1003- DatabaseEdgeType .HAS_OWNER .value ,
1004- DatabaseEdgeType .HAS_SOURCE .value ,
1005- ]:
1006- query = """
1014+ for edge_type in edge_types :
1015+ sub_query = """
10071016 CALL {
10081017 WITH rl
10091018 MATCH (rl)%(arrow_left)s[active_edge:%(edge_type)s]%(arrow_right)s(n)
10101019 WHERE %(active_rel_filter)s AND active_edge.status ="active"
10111020 CREATE (rl)%(arrow_left)s[deleted_edge:%(edge_type)s $rel_prop]%(arrow_right)s(n)
10121021 SET deleted_edge.hierarchy = active_edge.hierarchy
1013- WITH active_edge
1022+ WITH active_edge, n
10141023 WHERE active_edge.branch = $branch AND active_edge.to IS NULL
10151024 SET active_edge.to = $at
10161025 }
@@ -1020,4 +1029,92 @@ async def query_init(self, db: InfrahubDatabase, **kwargs) -> None:
10201029 "active_rel_filter" : active_rel_filter ,
10211030 "edge_type" : edge_type ,
10221031 }
1023- self .add_to_query (query )
1032+
1033+ query += sub_query
1034+
1035+ # We only want to return uuid/kind of `Node` connected through `IS_RELATED` edges.
1036+ query += """
1037+ CALL {
1038+ WITH rl
1039+ MATCH (rl)-[active_edge:IS_RELATED]->(n)
1040+ WHERE %(active_rel_filter)s AND active_edge.status ="active"
1041+ CREATE (rl)-[deleted_edge:IS_RELATED $rel_prop]->(n)
1042+ SET deleted_edge.hierarchy = active_edge.hierarchy
1043+ WITH rl, active_edge, n
1044+ WHERE active_edge.branch = $branch AND active_edge.to IS NULL
1045+ SET active_edge.to = $at
1046+ RETURN
1047+ n.uuid as uuid,
1048+ n.kind as kind,
1049+ rl.name as rel_identifier,
1050+ "outbound" as rel_direction
1051+
1052+ UNION
1053+
1054+ WITH rl
1055+ MATCH (rl)<-[active_edge:IS_RELATED]-(n)
1056+ WHERE %(active_rel_filter)s AND active_edge.status ="active"
1057+ CREATE (rl)<-[deleted_edge:IS_RELATED $rel_prop]-(n)
1058+ SET deleted_edge.hierarchy = active_edge.hierarchy
1059+ WITH rl, active_edge, n
1060+ WHERE active_edge.branch = $branch AND active_edge.to IS NULL
1061+ SET active_edge.to = $at
1062+ RETURN
1063+ n.uuid as uuid,
1064+ n.kind as kind,
1065+ rl.name as rel_identifier,
1066+ "inbound" as rel_direction
1067+ }
1068+ RETURN DISTINCT uuid, kind, rel_identifier, rel_direction
1069+ """ % {
1070+ "active_rel_filter" : active_rel_filter ,
1071+ }
1072+
1073+ self .add_to_query (query )
1074+
1075+ def get_deleted_relationships_changelog (
1076+ self , node_schema : NodeSchema
1077+ ) -> list [RelationshipCardinalityOneChangelog | RelationshipCardinalityManyChangelog ]:
1078+ rel_identifier_to_changelog_mapper = {}
1079+
1080+ for result in self .get_results ():
1081+ peer_uuid = result .data ["uuid" ]
1082+ if peer_uuid == self .node_id :
1083+ continue
1084+
1085+ rel_identifier = result .data ["rel_identifier" ]
1086+ kind = result .data ["kind" ]
1087+ deleted_rel_schemas = [
1088+ rel_schema for rel_schema in node_schema .relationships if rel_schema .identifier == rel_identifier
1089+ ]
1090+
1091+ if len (deleted_rel_schemas ) == 0 :
1092+ continue # TODO Unidirectional relationship changelog should be handled, cf IFC-1319.
1093+
1094+ if len (deleted_rel_schemas ) > 2 :
1095+ log .error (f"Duplicated relationship schema with identifier { rel_identifier } " )
1096+ continue
1097+
1098+ if len (deleted_rel_schemas ) == 2 :
1099+ # Hierarchical schema nodes have 2 relationships with `parent_child` identifiers,
1100+ # which are differentiated by their direction within the database.
1101+ # assert rel_identifier != PARENT_CHILD_IDENTIFIER
1102+
1103+ rel_direction = result .data ["rel_direction" ]
1104+ deleted_rel_schema = (
1105+ deleted_rel_schemas [0 ]
1106+ if deleted_rel_schemas [0 ].direction .value == rel_direction
1107+ else deleted_rel_schemas [1 ]
1108+ )
1109+ else :
1110+ deleted_rel_schema = deleted_rel_schemas [0 ]
1111+
1112+ try :
1113+ changelog_mapper = rel_identifier_to_changelog_mapper [rel_identifier ]
1114+ except KeyError :
1115+ changelog_mapper = ChangelogRelationshipMapper (schema = deleted_rel_schema )
1116+ rel_identifier_to_changelog_mapper [rel_identifier ] = changelog_mapper
1117+
1118+ changelog_mapper .delete_relationship (peer_id = peer_uuid , peer_kind = kind , rel_schema = deleted_rel_schema )
1119+
1120+ return [changelog_mapper .changelog for changelog_mapper in rel_identifier_to_changelog_mapper .values ()]
0 commit comments