Skip to content

Commit 1b76a36

Browse files
authored
Merge pull request #5809 from opsmill/lgu-fix-node-kind-migrate
Fix node kind migration with agnostic relationships
2 parents 26c84d1 + 0783099 commit 1b76a36

File tree

7 files changed

+320
-108
lines changed

7 files changed

+320
-108
lines changed

backend/infrahub/core/migrations/query/node_duplicate.py

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,27 +48,47 @@ def render_match(self) -> str:
4848

4949
@staticmethod
5050
def _render_sub_query_per_rel_type(
51-
rel_name: str, rel_type: str, rel_def: FieldInfo, direction: GraphRelDirection
51+
rel_name: str,
52+
rel_type: str,
53+
rel_def: FieldInfo,
5254
) -> str:
5355
subquery = [
5456
f"WITH peer_node, {rel_name}, active_node, new_node",
5557
f"WITH peer_node, {rel_name}, active_node, new_node",
5658
f'WHERE type({rel_name}) = "{rel_type}"',
5759
]
58-
if rel_def.default.direction in [direction, GraphRelDirection.EITHER]:
59-
subquery.append(f"CREATE (new_node)-[:{rel_type} $rel_props_new ]->(peer_node)")
60-
subquery.append(f"CREATE (active_node)-[:{rel_type} $rel_props_prev ]->(peer_node)")
61-
elif rel_def.default.direction in [direction, GraphRelDirection.EITHER]:
62-
subquery.append(f"CREATE (new_node)<-[:{rel_type} $rel_props_new ]-(peer_node)")
63-
subquery.append(f"CREATE (active_node)<-[:{rel_type} $rel_props_prev ]-(peer_node)")
60+
if rel_def.default.direction in [GraphRelDirection.OUTBOUND, GraphRelDirection.EITHER]:
61+
subquery.append(f"""
62+
CREATE (new_node)-[new_active_edge:{rel_type} $rel_props_new ]->(peer_node)
63+
SET new_active_edge.branch = CASE WHEN {rel_name}.branch = "-global-" THEN "-global-" ELSE $branch END
64+
SET new_active_edge.branch_level = CASE WHEN {rel_name}.branch = "-global-" THEN {rel_name}.branch_level ELSE $branch_level END
65+
""")
66+
subquery.append(f"""
67+
CREATE (active_node)-[deleted_edge:{rel_type} $rel_props_prev ]->(peer_node)
68+
SET deleted_edge.branch = CASE WHEN {rel_name}.branch = "-global-" THEN "-global-" ELSE $branch END
69+
SET deleted_edge.branch_level = CASE WHEN {rel_name}.branch = "-global-" THEN {rel_name}.branch_level ELSE $branch_level END
70+
""")
71+
elif rel_def.default.direction in [GraphRelDirection.INBOUND, GraphRelDirection.EITHER]:
72+
subquery.append(f"""
73+
CREATE (new_node)<-[new_active_edge:{rel_type} $rel_props_new ]-(peer_node)
74+
SET new_active_edge.branch = CASE WHEN {rel_name}.branch = "-global-" THEN "-global-" ELSE $branch END
75+
SET new_active_edge.branch_level = CASE WHEN {rel_name}.branch = "-global-" THEN {rel_name}.branch_level ELSE $branch_level END
76+
""")
77+
subquery.append(f"""
78+
CREATE (active_node)<-[deleted_edge:{rel_type} $rel_props_prev ]-(peer_node)
79+
SET new_active_edge.branch = CASE WHEN {rel_name}.branch = "-global-" THEN "-global-" ELSE $branch END
80+
SET new_active_edge.branch_level = CASE WHEN {rel_name}.branch = "-global-" THEN {rel_name}.branch_level ELSE $branch_level END
81+
""")
6482
subquery.append("RETURN peer_node as p2")
6583
return "\n".join(subquery)
6684

6785
@classmethod
6886
def _render_sub_query_out(cls) -> str:
6987
sub_queries_out = [
7088
cls._render_sub_query_per_rel_type(
71-
rel_name="rel_outband", rel_type=rel_type, rel_def=rel_def, direction=GraphRelDirection.OUTBOUND
89+
rel_name="rel_outband",
90+
rel_type=rel_type,
91+
rel_def=rel_def,
7292
)
7393
for rel_type, rel_def in GraphNodeRelationships.model_fields.items()
7494
]
@@ -79,7 +99,9 @@ def _render_sub_query_out(cls) -> str:
7999
def _render_sub_query_in(cls) -> str:
80100
sub_queries_in = [
81101
cls._render_sub_query_per_rel_type(
82-
rel_name="rel_inband", rel_type=rel_type, rel_def=rel_def, direction=GraphRelDirection.INBOUND
102+
rel_name="rel_inband",
103+
rel_type=rel_type,
104+
rel_def=rel_def,
83105
)
84106
for rel_type, rel_def in GraphNodeRelationships.model_fields.items()
85107
]
@@ -94,19 +116,16 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> No
94116
self.params["previous_node"] = self.previous_node.model_dump()
95117

96118
self.params["current_time"] = self.at.to_string()
97-
self.params["branch_name"] = self.branch.name
119+
self.params["branch"] = self.branch.name
120+
self.params["branch_level"] = self.branch.hierarchy_level
98121
self.params["branch_support"] = self.new_node.branch_support
99122

100123
self.params["rel_props_new"] = {
101-
"branch": self.branch.name,
102-
"branch_level": self.branch.hierarchy_level,
103124
"status": RelationshipStatus.ACTIVE.value,
104125
"from": self.at.to_string(),
105126
}
106127

107128
self.params["rel_props_prev"] = {
108-
"branch": self.branch.name,
109-
"branch_level": self.branch.hierarchy_level,
110129
"status": RelationshipStatus.DELETED.value,
111130
"from": self.at.to_string(),
112131
}
@@ -141,12 +160,12 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> No
141160
LIMIT 1
142161
}
143162
WITH n1 as active_node, rel_outband1 as rel_outband, p1 as peer_node, new_node
144-
WHERE rel_outband.status = "active"
163+
WHERE rel_outband.status = "active" AND rel_outband.to IS NULL
145164
CALL {
146165
%(sub_query_out)s
147166
}
148167
WITH p2 as peer_node, rel_outband, active_node, new_node
149-
FOREACH (i in CASE WHEN rel_outband.branch = $branch_name THEN [1] ELSE [] END |
168+
FOREACH (i in CASE WHEN rel_outband.branch IN ["-global-", $branch] THEN [1] ELSE [] END |
150169
SET rel_outband.to = $current_time
151170
)
152171
WITH active_node, new_node
@@ -160,14 +179,15 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> No
160179
LIMIT 1
161180
}
162181
WITH n1 as active_node, rel_inband1 as rel_inband, p1 as peer_node, new_node
163-
WHERE rel_inband.status = "active"
182+
WHERE rel_inband.status = "active" AND rel_inband.to IS NULL
164183
CALL {
165184
%(sub_query_in)s
166185
}
167186
WITH p2 as peer_node, rel_inband, active_node, new_node
168-
FOREACH (i in CASE WHEN rel_inband.branch = $branch_name THEN [1] ELSE [] END |
187+
FOREACH (i in CASE WHEN rel_inband.branch IN ["-global-", $branch] THEN [1] ELSE [] END |
169188
SET rel_inband.to = $current_time
170189
)
190+
171191
RETURN DISTINCT new_node
172192
""" % {
173193
"branch_filter": branch_filter,

backend/infrahub/core/migrations/schema/node_remove.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,28 @@
1515

1616
class NodeRemoveMigrationBaseQuery(MigrationQuery):
1717
def render_sub_query_per_rel_type(
18-
self, rel_name: str, rel_type: str, rel_def: FieldInfo, direction: GraphRelDirection
18+
self,
19+
rel_name: str,
20+
rel_type: str,
21+
rel_def: FieldInfo,
1922
) -> str:
2023
subquery = [
2124
f"WITH peer_node, {rel_name}, active_node",
2225
f"WITH peer_node, {rel_name}, active_node",
2326
f'WHERE type({rel_name}) = "{rel_type}"',
2427
]
25-
if rel_def.default.direction in [direction, GraphRelDirection.EITHER]:
26-
subquery.append(f"CREATE (active_node)-[:{rel_type} $rel_props ]->(peer_node)")
27-
elif rel_def.default.direction in [direction, GraphRelDirection.EITHER]:
28-
subquery.append(f"CREATE (active_node)<-[:{rel_type} $rel_props ]-(peer_node)")
28+
if rel_def.default.direction in [GraphRelDirection.OUTBOUND, GraphRelDirection.EITHER]:
29+
subquery.append(f"""
30+
CREATE (active_node)-[edge:{rel_type} $rel_props ]->(peer_node)
31+
SET edge.branch = CASE WHEN {rel_name}.branch = "-global-" THEN "-global-" ELSE $branch END
32+
SET edge.branch_level = CASE WHEN {rel_name}.branch = "-global-" THEN {rel_name}.branch_level ELSE $branch_level END
33+
""")
34+
elif rel_def.default.direction in [GraphRelDirection.INBOUND, GraphRelDirection.EITHER]:
35+
subquery.append(f"""
36+
CREATE (active_node)<-[edge:{rel_type} $rel_props ]-(peer_node)
37+
SET edge.branch = CASE WHEN {rel_name}.branch = "-global-" THEN "-global-" ELSE $branch END
38+
SET edge.branch_level = CASE WHEN {rel_name}.branch = "-global-" THEN {rel_name}.branch_level ELSE $branch_level END
39+
""")
2940
subquery.append("RETURN peer_node as p2")
3041
return "\n".join(subquery)
3142

@@ -38,10 +49,10 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> No
3849

3950
self.params["current_time"] = self.at.to_string()
4051
self.params["branch_name"] = self.branch.name
52+
self.params["branch"] = self.branch.name
53+
self.params["branch_level"] = self.branch.hierarchy_level
4154

4255
self.params["rel_props"] = {
43-
"branch": self.branch.name,
44-
"branch_level": self.branch.hierarchy_level,
4556
"status": RelationshipStatus.DELETED.value,
4657
"from": self.at.to_string(),
4758
}
@@ -99,7 +110,7 @@ def render_node_remove_query(self, branch_filter: str) -> str:
99110
%(sub_query)s
100111
}
101112
WITH p2 as peer_node, rel_inband, active_node
102-
FOREACH (i in CASE WHEN rel_inband.branch = $branch_name THEN [1] ELSE [] END |
113+
FOREACH (i in CASE WHEN rel_inband.branch IN ["-global-", $branch] THEN [1] ELSE [] END |
103114
SET rel_inband.to = $current_time
104115
)
105116
""" % {"sub_query": sub_query, "branch_filter": branch_filter}
@@ -108,7 +119,9 @@ def render_node_remove_query(self, branch_filter: str) -> str:
108119
def render_sub_query_in(self) -> str:
109120
sub_queries_in = [
110121
self.render_sub_query_per_rel_type(
111-
rel_name="rel_inband", rel_type=rel_type, rel_def=rel_def, direction=GraphRelDirection.INBOUND
122+
rel_name="rel_inband",
123+
rel_type=rel_type,
124+
rel_def=rel_def,
112125
)
113126
for rel_type, rel_def in GraphNodeRelationships.model_fields.items()
114127
]
@@ -142,8 +155,7 @@ def render_node_remove_query(self, branch_filter: str) -> str:
142155
CALL {
143156
%(sub_query)s
144157
}
145-
WITH p2 as peer_node, rel_outband, active_node
146-
FOREACH (i in CASE WHEN rel_outband.branch = $branch_name THEN [1] ELSE [] END |
158+
FOREACH (i in CASE WHEN rel_outband.branch IN ["-global-", $branch] THEN [1] ELSE [] END |
147159
SET rel_outband.to = $current_time
148160
)
149161
""" % {"sub_query": sub_query, "branch_filter": branch_filter}
@@ -153,7 +165,9 @@ def render_node_remove_query(self, branch_filter: str) -> str:
153165
def render_sub_query_out(self) -> str:
154166
sub_queries_out = [
155167
self.render_sub_query_per_rel_type(
156-
rel_name="rel_outband", rel_type=rel_type, rel_def=rel_def, direction=GraphRelDirection.OUTBOUND
168+
rel_name="rel_outband",
169+
rel_type=rel_type,
170+
rel_def=rel_def,
157171
)
158172
for rel_type, rel_def in GraphNodeRelationships.model_fields.items()
159173
]

backend/tests/conftest.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,3 +951,75 @@ def patch_services(helper):
951951
yield bus
952952
services.service.message_bus = original
953953
services.prepare(service=services.service)
954+
955+
956+
@pytest.fixture(scope="class")
957+
def car_person_branch_agnostic_schema() -> dict[str, Any]:
958+
schema: dict[str, Any] = {
959+
"version": "1.0",
960+
"nodes": [
961+
{
962+
"name": "Car",
963+
"namespace": "Test",
964+
"default_filter": "name__value",
965+
"uniqueness_constraints": [["name__value"]],
966+
"branch": BranchSupportType.AGNOSTIC.value,
967+
"attributes": [
968+
{"name": "name", "kind": "Text", "unique": True},
969+
],
970+
"relationships": [
971+
{
972+
"name": "owner",
973+
"label": "Commander of Car",
974+
"peer": "TestPerson",
975+
"optional": False,
976+
"kind": "Parent",
977+
"cardinality": "one",
978+
"direction": "outbound",
979+
"branch": BranchSupportType.AGNOSTIC.value,
980+
},
981+
],
982+
},
983+
{
984+
"name": "Person",
985+
"namespace": "Test",
986+
"default_filter": "name__value",
987+
"display_labels": ["name__value"],
988+
"branch": BranchSupportType.AWARE.value,
989+
"uniqueness_constraints": [["name__value"]],
990+
"attributes": [
991+
{"name": "name", "kind": "Text", "unique": True},
992+
],
993+
"relationships": [
994+
{
995+
"name": "cars",
996+
"peer": "TestCar",
997+
"cardinality": "many",
998+
"direction": "inbound",
999+
"branch": BranchSupportType.AGNOSTIC.value,
1000+
}
1001+
],
1002+
},
1003+
{
1004+
"name": "Roofrack",
1005+
"namespace": "Test",
1006+
"branch": BranchSupportType.AWARE.value,
1007+
"attributes": [
1008+
{"name": "size", "kind": "Text", "unique": True},
1009+
],
1010+
"relationships": [
1011+
{
1012+
"name": "car",
1013+
"label": "Commander of Car",
1014+
"peer": "TestCar",
1015+
"optional": False,
1016+
"kind": "Parent",
1017+
"cardinality": "one",
1018+
"direction": "outbound",
1019+
"branch": BranchSupportType.AGNOSTIC.value,
1020+
},
1021+
],
1022+
},
1023+
],
1024+
}
1025+
return schema

backend/tests/functional/branch/conftest.py

Lines changed: 0 additions & 76 deletions
This file was deleted.

0 commit comments

Comments
 (0)