Skip to content

Commit aaddd2c

Browse files
authored
Merge pull request #5883 from opsmill/jbr-conflits-02272025-stable-to-release12
Merge stable into release-1.2
2 parents 76d0edb + c5a0c81 commit aaddd2c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+468
-812
lines changed

backend/infrahub/core/changelog/diff.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def _process_node_cardinality_one_relationship(
177177
value_previous=rel_prop.previous_value,
178178
)
179179

180-
node.add_relationship(relationship=changelog_rel)
180+
node.add_relationship(relationship_changelog=changelog_rel)
181181

182182
def _convert_string_boolean_value(self, value: str | None) -> bool | None:
183183
"""Convert string based boolean for is_protected and is_visible."""
@@ -227,7 +227,7 @@ def _process_node_cardinality_many_relationship(
227227

228228
changelog_rel.peers.append(peer_log)
229229

230-
node.add_relationship(relationship=changelog_rel)
230+
node.add_relationship(relationship_changelog=changelog_rel)
231231

232232
def collect_changelogs(self) -> Sequence[tuple[DiffAction, NodeChangelog]]:
233233
self._populate_diff_nodes()

backend/infrahub/core/changelog/models.py

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ def add_property(self, name: str, value_current: bool | str | None, value_previo
129129
def set_parent(self, parent_id: str, parent_kind: str) -> None:
130130
self._parent = ChangelogRelatedNode(node_id=parent_id, node_kind=parent_kind)
131131

132-
def set_parent_from_relationship(self, relationship: Relationship) -> None:
133-
if relationship.schema.kind == RelationshipKind.PARENT:
132+
def set_parent_from_relationship(self, rel_kind: RelationshipKind) -> None:
133+
if rel_kind == RelationshipKind.PARENT:
134134
if (
135135
self.peer_status in [DiffAction.ADDED, DiffAction.UNCHANGED, DiffAction.UPDATED]
136136
and self.peer_id
@@ -308,14 +308,14 @@ def add_attribute(self, attribute: AttributeChangelog) -> None:
308308
self.attributes[attribute.name] = attribute
309309

310310
def add_relationship(
311-
self, relationship: RelationshipCardinalityOneChangelog | RelationshipCardinalityManyChangelog
311+
self, relationship_changelog: RelationshipCardinalityOneChangelog | RelationshipCardinalityManyChangelog
312312
) -> None:
313-
if isinstance(relationship, RelationshipCardinalityOneChangelog) and relationship.parent:
314-
self.add_parent(parent=relationship.parent)
315-
if relationship.is_empty:
313+
if isinstance(relationship_changelog, RelationshipCardinalityOneChangelog) and relationship_changelog.parent:
314+
self.add_parent(parent=relationship_changelog.parent)
315+
if relationship_changelog.is_empty:
316316
return
317317

318-
self.relationships[relationship.name] = relationship
318+
self.relationships[relationship_changelog.name] = relationship_changelog
319319

320320
def create_attribute(self, attribute: BaseAttribute) -> None:
321321
changelog_attribute = AttributeChangelog(
@@ -383,7 +383,7 @@ def remove_peer(self, peer_data: RelationshipPeerData) -> None:
383383
def _set_cardinality_one_peer(self, relationship: Relationship) -> None:
384384
self.cardinality_one_relationship.peer_id = relationship.peer_id
385385
self.cardinality_one_relationship.peer_kind = relationship.get_peer_kind()
386-
self.cardinality_one_relationship.set_parent_from_relationship(relationship=relationship)
386+
self.cardinality_one_relationship.set_parent_from_relationship(rel_kind=relationship.schema.kind)
387387

388388
def add_parent_from_relationship(self, relationship: Relationship) -> None:
389389
if self.schema.cardinality == RelationshipCardinality.ONE:
@@ -420,18 +420,16 @@ def add_updated_relationship(
420420
value_current=getattr(relationship, property_name),
421421
value_previous=previous_value,
422422
)
423-
self.cardinality_one_relationship.set_parent_from_relationship(relationship=relationship)
423+
self.cardinality_one_relationship.set_parent_from_relationship(rel_kind=relationship.schema.kind)
424424

425-
def delete_relationship(self, relationship: Relationship) -> None:
425+
def delete_relationship(self, peer_id: str, peer_kind: str, rel_schema: RelationshipSchema) -> None:
426426
if self.schema.cardinality == RelationshipCardinality.ONE:
427-
self.cardinality_one_relationship.peer_id_previous = relationship.get_peer_id()
428-
self.cardinality_one_relationship.peer_kind_previous = relationship.get_peer_kind()
429-
self.cardinality_one_relationship.set_parent_from_relationship(relationship=relationship)
427+
self.cardinality_one_relationship.peer_id_previous = peer_id
428+
self.cardinality_one_relationship.peer_kind_previous = peer_kind
429+
self.cardinality_one_relationship.set_parent_from_relationship(rel_kind=rel_schema.kind)
430430

431431
elif self.schema.cardinality == RelationshipCardinality.MANY:
432-
self.cardinality_many_relationship.remove_peer(
433-
peer_id=relationship.get_peer_id(), peer_kind=relationship.get_peer_kind()
434-
)
432+
self.cardinality_many_relationship.remove_peer(peer_id=peer_id, peer_kind=peer_kind)
435433

436434
@property
437435
def changelog(self) -> RelationshipCardinalityOneChangelog | RelationshipCardinalityManyChangelog:

backend/infrahub/core/constants/schema.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from enum import Enum, Flag, auto
22

3+
PARENT_CHILD_IDENTIFIER = "parent__child"
4+
35

46
class FlagProperty(Enum):
57
IS_VISIBLE = "is_visible"

backend/infrahub/core/diff/model/path.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ class EnrichedDiffAttribute(BaseSummary):
163163
def __hash__(self) -> int:
164164
return hash(self.name)
165165

166+
@property
167+
def num_properties(self) -> int:
168+
return len(self.properties)
169+
166170
def get_all_conflicts(self) -> dict[str, EnrichedDiffConflict]:
167171
return {prop.path_identifier: prop.conflict for prop in self.properties if prop.conflict}
168172

@@ -202,6 +206,10 @@ class EnrichedDiffSingleRelationship(BaseSummary):
202206
def __hash__(self) -> int:
203207
return hash(self.peer_id)
204208

209+
@property
210+
def num_properties(self) -> int:
211+
return len(self.properties)
212+
205213
def get_all_conflicts(self) -> dict[str, EnrichedDiffConflict]:
206214
all_conflicts: dict[str, EnrichedDiffConflict] = {}
207215
if self.conflict:
@@ -248,6 +256,10 @@ class EnrichedDiffRelationship(BaseSummary):
248256
def __hash__(self) -> int:
249257
return hash(self.name)
250258

259+
@property
260+
def num_properties(self) -> int:
261+
return sum(r.num_properties for r in self.relationships)
262+
251263
def get_all_conflicts(self) -> dict[str, EnrichedDiffConflict]:
252264
all_conflicts: dict[str, EnrichedDiffConflict] = {}
253265
for element in self.relationships:
@@ -308,6 +320,10 @@ class EnrichedDiffNode(BaseSummary):
308320
def __hash__(self) -> int:
309321
return hash(self.uuid)
310322

323+
@property
324+
def num_properties(self) -> int:
325+
return sum(a.num_properties for a in self.attributes) + sum(r.num_properties for r in self.relationships)
326+
311327
def get_all_conflicts(self) -> dict[str, EnrichedDiffConflict]:
312328
all_conflicts: dict[str, EnrichedDiffConflict] = {}
313329
if self.conflict:

backend/infrahub/core/diff/query/save.py

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -83,33 +83,55 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa
8383
query = """
8484
UNWIND $node_details_list AS node_details
8585
WITH node_details.root_uuid AS root_uuid, node_details.node_map AS node_map
86+
MATCH (diff_root:DiffRoot {uuid: root_uuid})
87+
MERGE (diff_root)-[:DIFF_HAS_NODE]->(diff_node:DiffNode {uuid: node_map.node_properties.uuid})
88+
WITH root_uuid, node_map, diff_node, (node_map.conflict_params IS NOT NULL) AS has_node_conflict
89+
SET
90+
diff_node.kind = node_map.node_properties.kind,
91+
diff_node.label = node_map.node_properties.label,
92+
diff_node.changed_at = node_map.node_properties.changed_at,
93+
diff_node.action = node_map.node_properties.action,
94+
diff_node.path_identifier = node_map.node_properties.path_identifier
95+
WITH root_uuid, node_map, diff_node, has_node_conflict
8696
CALL {
87-
WITH root_uuid, node_map
88-
MATCH (diff_root {uuid: root_uuid})
89-
MERGE (diff_root)-[:DIFF_HAS_NODE]->(diff_node:DiffNode {uuid: node_map.node_properties.uuid})
90-
SET
91-
diff_node.kind = node_map.node_properties.kind,
92-
diff_node.label = node_map.node_properties.label,
93-
diff_node.changed_at = node_map.node_properties.changed_at,
94-
diff_node.action = node_map.node_properties.action,
95-
diff_node.path_identifier = node_map.node_properties.path_identifier
9697
// -------------------------
97-
// add/remove node-level conflict
98+
// delete parent-child relationships for included nodes, they will be added in EnrichedNodesLinkQuery
9899
// -------------------------
99-
WITH diff_node, node_map
100-
OPTIONAL MATCH (diff_node)-[:DIFF_HAS_CONFLICT]->(current_diff_node_conflict:DiffConflict)
101-
WITH diff_node, node_map, current_diff_node_conflict, (node_map.conflict_params IS NOT NULL) AS has_node_conflict
102-
FOREACH (i in CASE WHEN has_node_conflict = FALSE THEN [1] ELSE [] END |
103-
DETACH DELETE current_diff_node_conflict
104-
)
105-
FOREACH (i in CASE WHEN has_node_conflict = TRUE THEN [1] ELSE [] END |
106-
MERGE (diff_node)-[:DIFF_HAS_CONFLICT]->(diff_node_conflict:DiffConflict)
107-
SET diff_node_conflict = node_map.conflict_params
108-
)
100+
WITH diff_node
101+
MATCH (diff_node)-[:DIFF_HAS_RELATIONSHIP]->(:DiffRelationship)-[parent_rel:DIFF_HAS_NODE]->(:DiffNode)
102+
DELETE parent_rel
103+
}
104+
OPTIONAL MATCH (diff_node)-[:DIFF_HAS_CONFLICT]->(current_node_conflict:DiffConflict)
105+
CALL {
106+
// -------------------------
107+
// create a node-level conflict, if necessary
108+
// -------------------------
109+
WITH diff_node, current_node_conflict, has_node_conflict
110+
WITH diff_node, current_node_conflict, has_node_conflict
111+
WHERE current_node_conflict IS NULL AND has_node_conflict = TRUE
112+
CREATE (diff_node)-[:DIFF_HAS_CONFLICT]->(:DiffConflict)
113+
}
114+
CALL {
115+
// -------------------------
116+
// delete a node-level conflict, if necessary
117+
// -------------------------
118+
WITH current_node_conflict, has_node_conflict
119+
WITH current_node_conflict, has_node_conflict
120+
WHERE current_node_conflict IS NOT NULL AND has_node_conflict = FALSE
121+
DETACH DELETE current_node_conflict
122+
}
123+
WITH root_uuid, node_map, diff_node, has_node_conflict, node_map.conflict_params AS node_conflict_params
124+
CALL {
125+
// -------------------------
126+
// set the properties of the node-level conflict, if necessary
127+
// -------------------------
128+
WITH diff_node, has_node_conflict, node_conflict_params
129+
WITH diff_node, has_node_conflict, node_conflict_params
130+
WHERE has_node_conflict = TRUE
131+
OPTIONAL MATCH (diff_node)-[:DIFF_HAS_CONFLICT]->(node_conflict:DiffConflict)
132+
SET node_conflict = node_conflict_params
109133
}
110134
CALL {
111-
WITH root_uuid, node_map
112-
MATCH (diff_root {uuid: root_uuid})-[:DIFF_HAS_NODE]->(diff_node:DiffNode {uuid: node_map.node_properties.uuid})
113135
// -------------------------
114136
// remove stale attributes for this node
115137
// -------------------------
@@ -426,14 +448,14 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa
426448
node_link_details.root_uuid AS root_uuid,
427449
node_link_details.parent_uuid AS parent_uuid,
428450
node_link_details.child_uuid AS child_uuid,
429-
node_link_details.relationship_name AS relationship_name
451+
node_link_details.child_relationship_name AS relationship_name
430452
CALL {
431453
WITH root_uuid, parent_uuid, child_uuid, relationship_name
432454
MATCH (diff_root {uuid: root_uuid})
433-
MATCH (diff_root)-[:DIFF_HAS_NODE]->(parent_node:DiffNode {uuid: parent_uuid})
434-
-[:DIFF_HAS_RELATIONSHIP]->(diff_rel_group:DiffRelationship {name: relationship_name})
435455
MATCH (diff_root)-[:DIFF_HAS_NODE]->(child_node:DiffNode {uuid: child_uuid})
436-
MERGE (diff_rel_group)-[:DIFF_HAS_NODE]->(child_node)
456+
-[:DIFF_HAS_RELATIONSHIP]->(diff_rel_group:DiffRelationship {name: relationship_name})
457+
MATCH (diff_root)-[:DIFF_HAS_NODE]->(parent_node:DiffNode {uuid: parent_uuid})
458+
MERGE (diff_rel_group)-[:DIFF_HAS_NODE]->(parent_node)
437459
}
438460
"""
439461
self.add_to_query(query)
@@ -443,14 +465,14 @@ def _build_node_parent_links(self, enriched_node: EnrichedDiffNode, root_uuid: s
443465
return []
444466
parent_links = []
445467
for relationship in enriched_node.relationships:
446-
for child_node in relationship.nodes:
468+
for parent_node in relationship.nodes:
447469
parent_links.append(
448470
{
449-
"parent_uuid": enriched_node.uuid,
450-
"relationship_name": relationship.name,
451-
"child_uuid": child_node.uuid,
471+
"child_uuid": enriched_node.uuid,
472+
"child_relationship_name": relationship.name,
473+
"parent_uuid": parent_node.uuid,
452474
"root_uuid": root_uuid,
453475
}
454476
)
455-
parent_links.extend(self._build_node_parent_links(enriched_node=child_node, root_uuid=root_uuid))
477+
parent_links.extend(self._build_node_parent_links(enriched_node=parent_node, root_uuid=root_uuid))
456478
return parent_links

backend/infrahub/core/diff/repository/repository.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from collections import defaultdict
22
from typing import AsyncGenerator, Generator, Iterable
33

4+
from neo4j.exceptions import TransientError
5+
46
from infrahub import config
57
from infrahub.core import registry
68
from infrahub.core.diff.query.field_summary import EnrichedDiffNodeFieldSummaryQuery
@@ -42,11 +44,10 @@
4244

4345

4446
class DiffRepository:
45-
MAX_SAVE_BATCH_SIZE: int = 100
46-
47-
def __init__(self, db: InfrahubDatabase, deserializer: EnrichedDiffDeserializer):
47+
def __init__(self, db: InfrahubDatabase, deserializer: EnrichedDiffDeserializer, max_save_batch_size: int = 1000):
4848
self.db = db
4949
self.deserializer = deserializer
50+
self.max_save_batch_size = max_save_batch_size
5051

5152
async def _run_get_diff_query(
5253
self,
@@ -230,20 +231,44 @@ def _get_node_create_request_batch(
230231
) -> Generator[list[EnrichedNodeCreateRequest], None, None]:
231232
node_requests = []
232233
for diff_root in (enriched_diffs.base_branch_diff, enriched_diffs.diff_branch_diff):
234+
size_count = 0
233235
for node in diff_root.nodes:
234-
node_requests.append(EnrichedNodeCreateRequest(node=node, root_uuid=diff_root.uuid))
235-
if len(node_requests) == self.MAX_SAVE_BATCH_SIZE:
236+
node_size_count = node.num_properties
237+
if size_count + node_size_count < self.max_save_batch_size:
238+
node_requests.append(EnrichedNodeCreateRequest(node=node, root_uuid=diff_root.uuid))
239+
size_count += node_size_count
240+
else:
241+
log.info(f"Num nodes in batch: {len(node_requests)}, num properties in batch: {size_count}")
236242
yield node_requests
237-
node_requests = []
243+
size_count = node_size_count
244+
node_requests = [EnrichedNodeCreateRequest(node=node, root_uuid=diff_root.uuid)]
238245
if node_requests:
246+
log.info(f"Num nodes in batch: {len(node_requests)}, num properties in batch: {size_count}")
239247
yield node_requests
240248

241-
@retry_db_transaction(name="enriched_diff_save")
242-
async def save(self, enriched_diffs: EnrichedDiffs | EnrichedDiffsMetadata, do_summary_counts: bool = True) -> None:
249+
@retry_db_transaction(name="enriched_diff_metadata_save")
250+
async def _save_root_metadata(self, enriched_diffs: EnrichedDiffsMetadata) -> None:
243251
log.info("Updating diff metadata...")
244252
root_query = await EnrichedDiffRootsUpsertQuery.init(db=self.db, enriched_diffs=enriched_diffs)
245253
await root_query.execute(db=self.db)
246254
log.info("Diff metadata updated.")
255+
256+
async def _save_node_batch(self, node_create_batch: list[EnrichedNodeCreateRequest]) -> None:
257+
node_query = await EnrichedNodeBatchCreateQuery.init(db=self.db, node_create_batch=node_create_batch)
258+
try:
259+
await node_query.execute(db=self.db)
260+
except TransientError as exc:
261+
if not exc.code or "OutOfMemoryError".lower() not in str(exc.code).lower():
262+
raise
263+
log.exception("Database memory error during save. Trying smaller transactions")
264+
for node_request in node_create_batch:
265+
single_node_query = await EnrichedNodeBatchCreateQuery.init(
266+
db=self.db, node_create_batch=[node_request]
267+
)
268+
await single_node_query.execute(db=self.db)
269+
270+
async def save(self, enriched_diffs: EnrichedDiffs | EnrichedDiffsMetadata, do_summary_counts: bool = True) -> None:
271+
await self._save_root_metadata(enriched_diffs=enriched_diffs)
247272
if not isinstance(enriched_diffs, EnrichedDiffs):
248273
return
249274
num_nodes = len(enriched_diffs.base_branch_diff.nodes) + len(enriched_diffs.diff_branch_diff.nodes)
@@ -252,9 +277,8 @@ async def save(self, enriched_diffs: EnrichedDiffs | EnrichedDiffsMetadata, do_s
252277
self._get_node_create_request_batch(enriched_diffs=enriched_diffs)
253278
):
254279
log.info(f"Saving node batch #{batch_num}...")
255-
node_query = await EnrichedNodeBatchCreateQuery.init(db=self.db, node_create_batch=node_create_batch)
256-
await node_query.execute(db=self.db)
257-
log.info(f"Batch #{batch_num} saved")
280+
await self._save_node_batch(node_create_batch=node_create_batch)
281+
log.info(f"Batch saved. num_nodes={len(node_create_batch)}")
258282
link_query = await EnrichedNodesLinkQuery.init(db=self.db, enriched_diffs=enriched_diffs)
259283
await link_query.execute(db=self.db)
260284
log.info("Diff saved.")
@@ -444,6 +468,7 @@ async def get_node_field_specifiers(self, diff_id: str) -> dict[str, set[str]]:
444468
offset += limit
445469
return specifiers
446470

471+
@retry_db_transaction(name="enriched_diff_summary_counts")
447472
async def add_summary_counts(
448473
self,
449474
diff_branch_name: str,
@@ -460,4 +485,4 @@ async def add_summary_counts(
460485
node_uuids=node_uuids,
461486
)
462487
await query.execute(db=self.db)
463-
log.info("Summary counts updated...")
488+
log.info("Summary counts updated.")

0 commit comments

Comments
 (0)