infrahub-server 1.1.6__py3-none-any.whl → 1.1.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. infrahub/core/attribute.py +4 -1
  2. infrahub/core/branch/tasks.py +7 -4
  3. infrahub/core/diff/combiner.py +11 -7
  4. infrahub/core/diff/coordinator.py +49 -70
  5. infrahub/core/diff/data_check_synchronizer.py +86 -7
  6. infrahub/core/diff/enricher/aggregated.py +3 -3
  7. infrahub/core/diff/enricher/cardinality_one.py +6 -6
  8. infrahub/core/diff/enricher/hierarchy.py +17 -4
  9. infrahub/core/diff/enricher/labels.py +18 -3
  10. infrahub/core/diff/enricher/path_identifier.py +7 -8
  11. infrahub/core/diff/merger/merger.py +5 -3
  12. infrahub/core/diff/model/path.py +66 -25
  13. infrahub/core/diff/parent_node_adder.py +78 -0
  14. infrahub/core/diff/payload_builder.py +13 -2
  15. infrahub/core/diff/query/all_conflicts.py +5 -2
  16. infrahub/core/diff/query/diff_get.py +2 -1
  17. infrahub/core/diff/query/field_specifiers.py +2 -0
  18. infrahub/core/diff/query/field_summary.py +2 -1
  19. infrahub/core/diff/query/filters.py +12 -1
  20. infrahub/core/diff/query/has_conflicts_query.py +5 -2
  21. infrahub/core/diff/query/{drop_tracking_id.py → merge_tracking_id.py} +3 -3
  22. infrahub/core/diff/query/roots_metadata.py +8 -1
  23. infrahub/core/diff/query/save.py +230 -139
  24. infrahub/core/diff/query/summary_counts_enricher.py +267 -0
  25. infrahub/core/diff/query/time_range_query.py +2 -1
  26. infrahub/core/diff/query_parser.py +49 -24
  27. infrahub/core/diff/repository/deserializer.py +31 -27
  28. infrahub/core/diff/repository/repository.py +215 -41
  29. infrahub/core/diff/tasks.py +4 -4
  30. infrahub/core/graph/__init__.py +1 -1
  31. infrahub/core/graph/index.py +3 -0
  32. infrahub/core/migrations/graph/__init__.py +4 -0
  33. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +256 -0
  34. infrahub/core/migrations/graph/m020_duplicate_edges.py +160 -0
  35. infrahub/core/migrations/query/node_duplicate.py +38 -18
  36. infrahub/core/migrations/schema/node_remove.py +26 -12
  37. infrahub/core/migrations/shared.py +10 -8
  38. infrahub/core/node/__init__.py +19 -9
  39. infrahub/core/node/constraints/grouped_uniqueness.py +25 -5
  40. infrahub/core/node/ipam.py +6 -1
  41. infrahub/core/node/permissions.py +4 -0
  42. infrahub/core/query/attribute.py +2 -0
  43. infrahub/core/query/diff.py +41 -3
  44. infrahub/core/query/node.py +74 -21
  45. infrahub/core/query/relationship.py +107 -17
  46. infrahub/core/query/resource_manager.py +5 -1
  47. infrahub/core/relationship/model.py +8 -12
  48. infrahub/core/schema/definitions/core.py +1 -0
  49. infrahub/core/utils.py +1 -0
  50. infrahub/core/validators/uniqueness/query.py +20 -17
  51. infrahub/database/__init__.py +14 -0
  52. infrahub/dependencies/builder/constraint/grouped/node_runner.py +0 -2
  53. infrahub/dependencies/builder/diff/coordinator.py +0 -2
  54. infrahub/dependencies/builder/diff/deserializer.py +3 -1
  55. infrahub/dependencies/builder/diff/enricher/hierarchy.py +3 -1
  56. infrahub/dependencies/builder/diff/parent_node_adder.py +8 -0
  57. infrahub/graphql/mutations/computed_attribute.py +3 -1
  58. infrahub/graphql/mutations/diff.py +41 -10
  59. infrahub/graphql/mutations/main.py +11 -6
  60. infrahub/graphql/mutations/relationship.py +29 -1
  61. infrahub/graphql/mutations/resource_manager.py +3 -3
  62. infrahub/graphql/mutations/tasks.py +6 -3
  63. infrahub/graphql/queries/resource_manager.py +7 -3
  64. infrahub/permissions/__init__.py +2 -1
  65. infrahub/permissions/types.py +26 -0
  66. infrahub_sdk/client.py +10 -2
  67. infrahub_sdk/config.py +3 -0
  68. infrahub_sdk/ctl/check.py +3 -3
  69. infrahub_sdk/ctl/cli_commands.py +16 -11
  70. infrahub_sdk/ctl/exceptions.py +0 -6
  71. infrahub_sdk/ctl/exporter.py +1 -1
  72. infrahub_sdk/ctl/generator.py +5 -5
  73. infrahub_sdk/ctl/importer.py +3 -2
  74. infrahub_sdk/ctl/menu.py +1 -1
  75. infrahub_sdk/ctl/object.py +1 -1
  76. infrahub_sdk/ctl/repository.py +23 -15
  77. infrahub_sdk/ctl/schema.py +2 -2
  78. infrahub_sdk/ctl/utils.py +4 -3
  79. infrahub_sdk/ctl/validate.py +2 -1
  80. infrahub_sdk/exceptions.py +12 -0
  81. infrahub_sdk/generator.py +3 -0
  82. infrahub_sdk/node.py +7 -4
  83. infrahub_sdk/testing/schemas/animal.py +9 -0
  84. infrahub_sdk/utils.py +11 -1
  85. infrahub_sdk/yaml.py +2 -3
  86. {infrahub_server-1.1.6.dist-info → infrahub_server-1.1.8.dist-info}/METADATA +41 -7
  87. {infrahub_server-1.1.6.dist-info → infrahub_server-1.1.8.dist-info}/RECORD +94 -91
  88. infrahub_testcontainers/container.py +12 -3
  89. infrahub_testcontainers/docker-compose.test.yml +22 -3
  90. infrahub_testcontainers/haproxy.cfg +43 -0
  91. infrahub_testcontainers/helpers.py +85 -1
  92. infrahub/core/diff/enricher/summary_counts.py +0 -105
  93. infrahub/dependencies/builder/diff/enricher/summary_counts.py +0 -8
  94. infrahub_sdk/ctl/_file.py +0 -13
  95. {infrahub_server-1.1.6.dist-info → infrahub_server-1.1.8.dist-info}/LICENSE.txt +0 -0
  96. {infrahub_server-1.1.6.dist-info → infrahub_server-1.1.8.dist-info}/WHEEL +0 -0
  97. {infrahub_server-1.1.6.dist-info → infrahub_server-1.1.8.dist-info}/entry_points.txt +0 -0
@@ -1,9 +1,15 @@
1
1
  from collections import defaultdict
2
- from typing import AsyncGenerator, Generator
2
+ from typing import AsyncGenerator, Generator, Iterable
3
+
4
+ from neo4j.exceptions import TransientError
3
5
 
4
6
  from infrahub import config
5
7
  from infrahub.core import registry
6
8
  from infrahub.core.diff.query.field_summary import EnrichedDiffNodeFieldSummaryQuery
9
+ from infrahub.core.diff.query.summary_counts_enricher import (
10
+ DiffFieldsSummaryCountsEnricherQuery,
11
+ DiffNodesSummaryCountsEnricherQuery,
12
+ )
7
13
  from infrahub.core.query.diff import DiffCountChanges
8
14
  from infrahub.core.timestamp import Timestamp
9
15
  from infrahub.database import InfrahubDatabase, retry_db_transaction
@@ -13,6 +19,7 @@ from infrahub.log import get_logger
13
19
  from ..model.path import (
14
20
  ConflictSelection,
15
21
  EnrichedDiffConflict,
22
+ EnrichedDiffNode,
16
23
  EnrichedDiffRoot,
17
24
  EnrichedDiffRootMetadata,
18
25
  EnrichedDiffs,
@@ -26,13 +33,13 @@ from ..query.all_conflicts import EnrichedDiffAllConflictsQuery
26
33
  from ..query.delete_query import EnrichedDiffDeleteQuery
27
34
  from ..query.diff_get import EnrichedDiffGetQuery
28
35
  from ..query.diff_summary import DiffSummaryCounters, DiffSummaryQuery
29
- from ..query.drop_tracking_id import EnrichedDiffDropTrackingIdQuery
30
36
  from ..query.field_specifiers import EnrichedDiffFieldSpecifiersQuery
31
37
  from ..query.filters import EnrichedDiffQueryFilters
32
38
  from ..query.get_conflict_query import EnrichedDiffConflictQuery
33
39
  from ..query.has_conflicts_query import EnrichedDiffHasConflictQuery
40
+ from ..query.merge_tracking_id import EnrichedDiffMergedTrackingIdQuery
34
41
  from ..query.roots_metadata import EnrichedDiffRootsMetadataQuery
35
- from ..query.save import EnrichedDiffRootsCreateQuery, EnrichedNodeBatchCreateQuery, EnrichedNodesLinkQuery
42
+ from ..query.save import EnrichedDiffRootsUpsertQuery, EnrichedNodeBatchCreateQuery, EnrichedNodesLinkQuery
36
43
  from ..query.time_range_query import EnrichedDiffTimeRangeQuery
37
44
  from ..query.update_conflict_query import EnrichedDiffConflictUpdateQuery
38
45
  from .deserializer import EnrichedDiffDeserializer
@@ -41,17 +48,17 @@ log = get_logger()
41
48
 
42
49
 
43
50
  class DiffRepository:
44
- MAX_SAVE_BATCH_SIZE: int = 100
45
-
46
- def __init__(self, db: InfrahubDatabase, deserializer: EnrichedDiffDeserializer):
51
+ def __init__(self, db: InfrahubDatabase, deserializer: EnrichedDiffDeserializer, max_save_batch_size: int = 1000):
47
52
  self.db = db
48
53
  self.deserializer = deserializer
54
+ self.max_save_batch_size = max_save_batch_size
49
55
 
50
56
  async def _run_get_diff_query(
51
57
  self,
52
58
  base_branch_name: str,
53
59
  diff_branch_names: list[str],
54
- limit: int,
60
+ batch_size_limit: int,
61
+ limit: int | None = None,
55
62
  from_time: Timestamp | None = None,
56
63
  to_time: Timestamp | None = None,
57
64
  filters: EnrichedDiffQueryFilters | None = None,
@@ -62,8 +69,13 @@ class DiffRepository:
62
69
  diff_ids: list[str] | None = None,
63
70
  ) -> list[EnrichedDiffRoot]:
64
71
  self.deserializer.initialize()
72
+ final_row_number = None
73
+ if limit:
74
+ final_row_number = offset + limit
65
75
  has_more_data = True
66
- while has_more_data:
76
+ while has_more_data and (final_row_number is None or offset < final_row_number):
77
+ if final_row_number is not None and offset + batch_size_limit > final_row_number:
78
+ batch_size_limit = final_row_number - offset
67
79
  get_query = await EnrichedDiffGetQuery.init(
68
80
  db=self.db,
69
81
  base_branch_name=base_branch_name,
@@ -72,12 +84,12 @@ class DiffRepository:
72
84
  to_time=to_time,
73
85
  filters=filters,
74
86
  max_depth=max_depth,
75
- limit=limit,
87
+ limit=batch_size_limit,
76
88
  offset=offset,
77
89
  tracking_id=tracking_id,
78
90
  diff_ids=diff_ids,
79
91
  )
80
- log.info(f"Beginning enriched diff get query {limit=}, {offset=}")
92
+ log.info(f"Beginning enriched diff get query {batch_size_limit=}, {offset=}")
81
93
  await get_query.execute(db=self.db)
82
94
  log.info("Enriched diff get query complete")
83
95
  last_result = None
@@ -87,7 +99,7 @@ class DiffRepository:
87
99
  has_more_data = False
88
100
  if last_result:
89
101
  has_more_data = last_result.get_as_type("has_more_data", bool)
90
- offset += limit
102
+ offset += batch_size_limit
91
103
  return await self.deserializer.deserialize()
92
104
 
93
105
  async def get(
@@ -105,16 +117,17 @@ class DiffRepository:
105
117
  include_empty: bool = False,
106
118
  ) -> list[EnrichedDiffRoot]:
107
119
  final_max_depth = config.SETTINGS.database.max_depth_search_hierarchy
108
- limit = limit or int(config.SETTINGS.database.query_size_limit / 10)
120
+ batch_size_limit = int(config.SETTINGS.database.query_size_limit / 10)
109
121
  diff_roots = await self._run_get_diff_query(
110
122
  base_branch_name=base_branch_name,
111
123
  diff_branch_names=diff_branch_names,
124
+ batch_size_limit=batch_size_limit,
125
+ limit=limit,
112
126
  from_time=from_time,
113
127
  to_time=to_time,
114
128
  filters=EnrichedDiffQueryFilters(**dict(filters or {})),
115
129
  include_parents=include_parents,
116
130
  max_depth=final_max_depth,
117
- limit=limit,
118
131
  offset=offset or 0,
119
132
  tracking_id=tracking_id,
120
133
  diff_ids=diff_ids,
@@ -131,42 +144,56 @@ class DiffRepository:
131
144
  to_time: Timestamp,
132
145
  ) -> list[EnrichedDiffs]:
133
146
  max_depth = config.SETTINGS.database.max_depth_search_hierarchy
134
- limit = int(config.SETTINGS.database.query_size_limit / 10)
147
+ batch_size_limit = int(config.SETTINGS.database.query_size_limit / 10)
135
148
  diff_branch_roots = await self._run_get_diff_query(
136
149
  base_branch_name=base_branch_name,
137
150
  diff_branch_names=[diff_branch_name],
138
151
  from_time=from_time,
139
152
  to_time=to_time,
140
153
  max_depth=max_depth,
141
- limit=limit,
154
+ batch_size_limit=batch_size_limit,
142
155
  )
143
156
  diffs_by_uuid = {dbr.uuid: dbr for dbr in diff_branch_roots}
144
157
  base_branch_roots = await self._run_get_diff_query(
145
158
  base_branch_name=base_branch_name,
146
159
  diff_branch_names=[base_branch_name],
147
160
  max_depth=max_depth,
148
- limit=limit,
149
- diff_ids=[d.partner_uuid for d in diffs_by_uuid.values()],
161
+ batch_size_limit=batch_size_limit,
162
+ diff_ids=[d.partner_uuid for d in diffs_by_uuid.values() if d.partner_uuid],
150
163
  )
151
164
  diffs_by_uuid.update({bbr.uuid: bbr for bbr in base_branch_roots})
152
- return [
153
- EnrichedDiffs(
154
- base_branch_name=base_branch_name,
155
- diff_branch_name=diff_branch_name,
156
- base_branch_diff=diffs_by_uuid[dbr.partner_uuid],
157
- diff_branch_diff=dbr,
165
+ diff_pairs = []
166
+ for dbr in diff_branch_roots:
167
+ if dbr.partner_uuid is None:
168
+ continue
169
+ base_branch_diff = diffs_by_uuid[dbr.partner_uuid]
170
+ diff_pairs.append(
171
+ EnrichedDiffs(
172
+ base_branch_name=base_branch_name,
173
+ diff_branch_name=diff_branch_name,
174
+ base_branch_diff=base_branch_diff,
175
+ diff_branch_diff=dbr,
176
+ )
158
177
  )
159
- for dbr in diff_branch_roots
160
- ]
178
+ return diff_pairs
161
179
 
162
- async def hydrate_diff_pair(self, enriched_diffs_metadata: EnrichedDiffsMetadata) -> EnrichedDiffs:
180
+ async def hydrate_diff_pair(
181
+ self,
182
+ enriched_diffs_metadata: EnrichedDiffsMetadata,
183
+ node_uuids: Iterable[str] | None = None,
184
+ ) -> EnrichedDiffs:
185
+ filters = None
186
+ if node_uuids:
187
+ filters = {"ids": list(node_uuids) if node_uuids is not None else None}
163
188
  hydrated_base_diff = await self.get_one(
164
189
  diff_branch_name=enriched_diffs_metadata.base_branch_name,
165
190
  diff_id=enriched_diffs_metadata.base_branch_diff.uuid,
191
+ filters=filters,
166
192
  )
167
193
  hydrated_branch_diff = await self.get_one(
168
194
  diff_branch_name=enriched_diffs_metadata.diff_branch_name,
169
195
  diff_id=enriched_diffs_metadata.diff_branch_diff.uuid,
196
+ filters=filters,
170
197
  )
171
198
  return EnrichedDiffs(
172
199
  base_branch_name=enriched_diffs_metadata.base_branch_name,
@@ -208,26 +235,111 @@ class DiffRepository:
208
235
  ) -> Generator[list[EnrichedNodeCreateRequest], None, None]:
209
236
  node_requests = []
210
237
  for diff_root in (enriched_diffs.base_branch_diff, enriched_diffs.diff_branch_diff):
238
+ size_count = 0
211
239
  for node in diff_root.nodes:
212
- node_requests.append(EnrichedNodeCreateRequest(node=node, root_uuid=diff_root.uuid))
213
- if len(node_requests) == self.MAX_SAVE_BATCH_SIZE:
240
+ node_size_count = node.num_properties
241
+ if size_count + node_size_count < self.max_save_batch_size:
242
+ node_requests.append(EnrichedNodeCreateRequest(node=node, root_uuid=diff_root.uuid))
243
+ size_count += node_size_count
244
+ else:
245
+ log.info(f"Num nodes in batch: {len(node_requests)}, num properties in batch: {size_count}")
214
246
  yield node_requests
215
- node_requests = []
247
+ size_count = node_size_count
248
+ node_requests = [EnrichedNodeCreateRequest(node=node, root_uuid=diff_root.uuid)]
216
249
  if node_requests:
250
+ log.info(f"Num nodes in batch: {len(node_requests)}, num properties in batch: {size_count}")
217
251
  yield node_requests
218
252
 
219
- @retry_db_transaction(name="enriched_diff_save")
220
- async def save(self, enriched_diffs: EnrichedDiffs) -> None:
221
- num_nodes = len(enriched_diffs.base_branch_diff.nodes) + len(enriched_diffs.diff_branch_diff.nodes)
222
- log.info(f"Saving diff (num_nodes={num_nodes})...")
223
- root_query = await EnrichedDiffRootsCreateQuery.init(db=self.db, enriched_diffs=enriched_diffs)
253
+ @retry_db_transaction(name="enriched_diff_metadata_save")
254
+ async def _save_root_metadata(self, enriched_diffs: EnrichedDiffsMetadata) -> None:
255
+ log.info("Updating diff metadata...")
256
+ root_query = await EnrichedDiffRootsUpsertQuery.init(db=self.db, enriched_diffs=enriched_diffs)
224
257
  await root_query.execute(db=self.db)
225
- for node_create_batch in self._get_node_create_request_batch(enriched_diffs=enriched_diffs):
226
- node_query = await EnrichedNodeBatchCreateQuery.init(db=self.db, node_create_batch=node_create_batch)
258
+ log.info("Diff metadata updated.")
259
+
260
+ async def _save_node_batch(self, node_create_batch: list[EnrichedNodeCreateRequest]) -> None:
261
+ node_query = await EnrichedNodeBatchCreateQuery.init(db=self.db, node_create_batch=node_create_batch)
262
+ try:
227
263
  await node_query.execute(db=self.db)
228
- link_query = await EnrichedNodesLinkQuery.init(db=self.db, enriched_diffs=enriched_diffs)
264
+ except TransientError as exc:
265
+ if not exc.code or "OutOfMemoryError".lower() not in str(exc.code).lower():
266
+ raise
267
+ log.exception("Database memory error during save. Trying smaller transactions")
268
+ for node_request in node_create_batch:
269
+ log.info(
270
+ f"Updating node {node_request.node.uuid}, num_properties={node_request.node.num_properties}..."
271
+ )
272
+ single_node_query = await EnrichedNodeBatchCreateQuery.init(
273
+ db=self.db, node_create_batch=[node_request]
274
+ )
275
+ await single_node_query.execute(db=self.db)
276
+
277
+ @retry_db_transaction(name="enriched_diff_hierarchy_update")
278
+ async def _run_hierarchy_links_update_query(self, diff_root_uuid: str, diff_nodes: list[EnrichedDiffNode]) -> None:
279
+ log.info(f"Updating diff hierarchy links, num_nodes={len(diff_nodes)}")
280
+ link_query = await EnrichedNodesLinkQuery.init(db=self.db, diff_root_uuid=diff_root_uuid, diff_nodes=diff_nodes)
229
281
  await link_query.execute(db=self.db)
230
- log.info("Diff saved.")
282
+
283
+ async def _update_hierarchy_links(self, enriched_diffs: EnrichedDiffs) -> None:
284
+ for diff_root in (enriched_diffs.base_branch_diff, enriched_diffs.diff_branch_diff):
285
+ nodes_to_update = []
286
+ for node in diff_root.nodes:
287
+ if any(r.nodes for r in node.relationships):
288
+ nodes_to_update.append(node)
289
+ if len(nodes_to_update) >= config.SETTINGS.database.query_size_limit:
290
+ await self._run_hierarchy_links_update_query(
291
+ diff_root_uuid=diff_root.uuid, diff_nodes=nodes_to_update
292
+ )
293
+ nodes_to_update = []
294
+ if nodes_to_update:
295
+ await self._run_hierarchy_links_update_query(diff_root_uuid=diff_root.uuid, diff_nodes=nodes_to_update)
296
+
297
+ async def _update_summary_counts(self, diff_root: EnrichedDiffRoot) -> None:
298
+ max_nodes_limit = config.SETTINGS.database.query_size_limit
299
+ num_nodes = len(diff_root.nodes)
300
+ if diff_root.exists_on_database and num_nodes < max_nodes_limit:
301
+ await self.add_summary_counts(
302
+ diff_branch_name=diff_root.diff_branch_name,
303
+ diff_id=diff_root.uuid,
304
+ node_uuids=None,
305
+ )
306
+ return
307
+ node_uuids: list[str] = []
308
+ for diff_node in diff_root.nodes:
309
+ node_uuids.append(diff_node.uuid)
310
+ if len(node_uuids) >= max_nodes_limit:
311
+ await self.add_summary_counts(
312
+ diff_branch_name=diff_root.diff_branch_name,
313
+ diff_id=diff_root.uuid,
314
+ node_uuids=node_uuids,
315
+ )
316
+ node_uuids = []
317
+ if node_uuids:
318
+ await self.add_summary_counts(
319
+ diff_branch_name=diff_root.diff_branch_name,
320
+ diff_id=diff_root.uuid,
321
+ node_uuids=node_uuids,
322
+ )
323
+
324
+ async def save(self, enriched_diffs: EnrichedDiffs | EnrichedDiffsMetadata, do_summary_counts: bool = True) -> None:
325
+ # metadata-only update
326
+ if not isinstance(enriched_diffs, EnrichedDiffs):
327
+ await self._save_root_metadata(enriched_diffs=enriched_diffs)
328
+ return
329
+
330
+ count_nodes_remaining = len(enriched_diffs.base_branch_diff.nodes) + len(enriched_diffs.diff_branch_diff.nodes)
331
+ log.info(f"Saving diff (num_nodes={count_nodes_remaining})...")
332
+ for batch_num, node_create_batch in enumerate(
333
+ self._get_node_create_request_batch(enriched_diffs=enriched_diffs)
334
+ ):
335
+ log.info(f"Saving node batch #{batch_num}...")
336
+ await self._save_node_batch(node_create_batch=node_create_batch)
337
+ count_nodes_remaining -= len(node_create_batch)
338
+ log.info(f"Batch saved. {count_nodes_remaining=}")
339
+ await self._update_hierarchy_links(enriched_diffs=enriched_diffs)
340
+ if do_summary_counts:
341
+ await self._update_summary_counts(diff_root=enriched_diffs.diff_branch_diff)
342
+ await self._save_root_metadata(enriched_diffs=enriched_diffs)
231
343
 
232
344
  async def summary(
233
345
  self,
@@ -277,6 +389,7 @@ class DiffRepository:
277
389
  base_branch_names: list[str] | None = None,
278
390
  from_time: Timestamp | None = None,
279
391
  to_time: Timestamp | None = None,
392
+ tracking_id: TrackingId | None = None,
280
393
  ) -> list[EnrichedDiffsMetadata]:
281
394
  if diff_branch_names and base_branch_names:
282
395
  diff_branch_names += base_branch_names
@@ -285,11 +398,12 @@ class DiffRepository:
285
398
  base_branch_names=base_branch_names,
286
399
  from_time=from_time,
287
400
  to_time=to_time,
401
+ tracking_id=tracking_id,
288
402
  )
289
403
  roots_by_id = {root.uuid: root for root in empty_roots}
290
404
  pairs: list[EnrichedDiffsMetadata] = []
291
405
  for branch_root in empty_roots:
292
- if branch_root.base_branch_name == branch_root.diff_branch_name:
406
+ if branch_root.base_branch_name == branch_root.diff_branch_name or branch_root.partner_uuid is None:
293
407
  continue
294
408
  base_root = roots_by_id[branch_root.partner_uuid]
295
409
  pairs.append(
@@ -308,6 +422,7 @@ class DiffRepository:
308
422
  base_branch_names: list[str] | None = None,
309
423
  from_time: Timestamp | None = None,
310
424
  to_time: Timestamp | None = None,
425
+ tracking_id: TrackingId | None = None,
311
426
  ) -> list[EnrichedDiffRootMetadata]:
312
427
  query = await EnrichedDiffRootsMetadataQuery.init(
313
428
  db=self.db,
@@ -315,6 +430,7 @@ class DiffRepository:
315
430
  base_branch_names=base_branch_names,
316
431
  from_time=from_time,
317
432
  to_time=to_time,
433
+ tracking_id=tracking_id,
318
434
  )
319
435
  await query.execute(db=self.db)
320
436
  diff_roots = []
@@ -374,8 +490,8 @@ class DiffRepository:
374
490
  await query.execute(db=self.db)
375
491
  return await query.get_field_summaries()
376
492
 
377
- async def drop_tracking_ids(self, tracking_ids: list[TrackingId]) -> None:
378
- query = await EnrichedDiffDropTrackingIdQuery.init(db=self.db, tracking_ids=tracking_ids)
493
+ async def mark_tracking_ids_merged(self, tracking_ids: list[TrackingId]) -> None:
494
+ query = await EnrichedDiffMergedTrackingIdQuery.init(db=self.db, tracking_ids=tracking_ids)
379
495
  await query.execute(db=self.db)
380
496
 
381
497
  async def get_num_changes_in_time_range_by_branch(
@@ -400,3 +516,61 @@ class DiffRepository:
400
516
  break
401
517
  offset += limit
402
518
  return specifiers
519
+
520
+ async def add_summary_counts(
521
+ self,
522
+ diff_branch_name: str,
523
+ tracking_id: TrackingId | None = None,
524
+ diff_id: str | None = None,
525
+ node_uuids: list[str] | None = None,
526
+ ) -> None:
527
+ await self._add_field_summary_counts(
528
+ diff_branch_name=diff_branch_name,
529
+ tracking_id=tracking_id,
530
+ diff_id=diff_id,
531
+ node_uuids=node_uuids,
532
+ )
533
+ await self._add_node_summary_counts(
534
+ diff_branch_name=diff_branch_name,
535
+ tracking_id=tracking_id,
536
+ diff_id=diff_id,
537
+ node_uuids=node_uuids,
538
+ )
539
+
540
+ @retry_db_transaction(name="enriched_diff_field_summary_counts")
541
+ async def _add_field_summary_counts(
542
+ self,
543
+ diff_branch_name: str,
544
+ tracking_id: TrackingId | None = None,
545
+ diff_id: str | None = None,
546
+ node_uuids: list[str] | None = None,
547
+ ) -> None:
548
+ log.info("Updating field summary counts...")
549
+ query = await DiffFieldsSummaryCountsEnricherQuery.init(
550
+ db=self.db,
551
+ diff_branch_name=diff_branch_name,
552
+ tracking_id=tracking_id,
553
+ diff_id=diff_id,
554
+ node_uuids=node_uuids,
555
+ )
556
+ await query.execute(db=self.db)
557
+ log.info("Field summary counts updated.")
558
+
559
+ @retry_db_transaction(name="enriched_diff_node_summary_counts")
560
+ async def _add_node_summary_counts(
561
+ self,
562
+ diff_branch_name: str,
563
+ tracking_id: TrackingId | None = None,
564
+ diff_id: str | None = None,
565
+ node_uuids: list[str] | None = None,
566
+ ) -> None:
567
+ log.info("Updating node summary counts...")
568
+ query = await DiffNodesSummaryCountsEnricherQuery.init(
569
+ db=self.db,
570
+ diff_branch_name=diff_branch_name,
571
+ tracking_id=tracking_id,
572
+ diff_id=diff_id,
573
+ node_uuids=node_uuids,
574
+ )
575
+ await query.execute(db=self.db)
576
+ log.info("node summary counts updated.")
@@ -8,7 +8,7 @@ from infrahub.dependencies.registry import get_component_registry
8
8
  from infrahub.log import get_logger
9
9
  from infrahub.services import services
10
10
  from infrahub.workflows.catalogue import DIFF_REFRESH
11
- from infrahub.workflows.utils import add_branch_tag
11
+ from infrahub.workflows.utils import add_tags
12
12
 
13
13
  log = get_logger()
14
14
 
@@ -16,7 +16,7 @@ log = get_logger()
16
16
  @flow(name="diff-update", flow_run_name="Update diff for branch {model.branch_name}")
17
17
  async def update_diff(model: RequestDiffUpdate) -> None:
18
18
  service = services.service
19
- await add_branch_tag(branch_name=model.branch_name)
19
+ await add_tags(branches=[model.branch_name])
20
20
 
21
21
  async with service.database.start_session() as db:
22
22
  component_registry = get_component_registry()
@@ -37,7 +37,7 @@ async def update_diff(model: RequestDiffUpdate) -> None:
37
37
  @flow(name="diff-refresh", flow_run_name="Recreate diff for branch {branch_name}")
38
38
  async def refresh_diff(branch_name: str, diff_id: str) -> None:
39
39
  service = services.service
40
- await add_branch_tag(branch_name=branch_name)
40
+ await add_tags(branches=[branch_name])
41
41
 
42
42
  async with service.database.start_session() as db:
43
43
  component_registry = get_component_registry()
@@ -51,7 +51,7 @@ async def refresh_diff(branch_name: str, diff_id: str) -> None:
51
51
  @flow(name="diff-refresh-all", flow_run_name="Recreate all diffs for branch {branch_name}")
52
52
  async def refresh_diff_all(branch_name: str) -> None:
53
53
  service = services.service
54
- await add_branch_tag(branch_name=branch_name)
54
+ await add_tags(branches=[branch_name])
55
55
 
56
56
  async with service.database.start_session() as db:
57
57
  component_registry = get_component_registry()
@@ -1 +1 @@
1
- GRAPH_VERSION = 18
1
+ GRAPH_VERSION = 20
@@ -12,6 +12,9 @@ node_indexes: list[IndexItem] = [
12
12
  IndexItem(name="attr_iphost_bin", label="AttributeIPHost", properties=["binary_address"], type=IndexType.RANGE),
13
13
  IndexItem(name="rel_uuid", label="Relationship", properties=["uuid"], type=IndexType.RANGE),
14
14
  IndexItem(name="rel_identifier", label="Relationship", properties=["name"], type=IndexType.RANGE),
15
+ # diff indices
16
+ IndexItem(name="diff_uuid", label="DiffRoot", properties=["uuid"], type=IndexType.TEXT),
17
+ IndexItem(name="diff_node_uuid", label="DiffNode", properties=["uuid"], type=IndexType.TEXT),
15
18
  ]
16
19
 
17
20
  rel_indexes: list[IndexItem] = [
@@ -20,6 +20,8 @@ from .m015_diff_format_update import Migration015
20
20
  from .m016_diff_delete_bug_fix import Migration016
21
21
  from .m017_add_core_profile import Migration017
22
22
  from .m018_uniqueness_nulls import Migration018
23
+ from .m019_restore_rels_to_time import Migration019
24
+ from .m020_duplicate_edges import Migration020
23
25
 
24
26
  if TYPE_CHECKING:
25
27
  from infrahub.core.root import Root
@@ -45,6 +47,8 @@ MIGRATIONS: list[type[Union[GraphMigration, InternalSchemaMigration, ArbitraryMi
45
47
  Migration016,
46
48
  Migration017,
47
49
  Migration018,
50
+ Migration019,
51
+ Migration020,
48
52
  ]
49
53
 
50
54