infrahub-server 1.2.9rc0__py3-none-any.whl → 1.3.0a0__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 (166) hide show
  1. infrahub/actions/constants.py +86 -0
  2. infrahub/actions/gather.py +114 -0
  3. infrahub/actions/models.py +241 -0
  4. infrahub/actions/parsers.py +104 -0
  5. infrahub/actions/schema.py +382 -0
  6. infrahub/actions/tasks.py +126 -0
  7. infrahub/actions/triggers.py +21 -0
  8. infrahub/cli/db.py +1 -2
  9. infrahub/computed_attribute/models.py +13 -0
  10. infrahub/computed_attribute/tasks.py +48 -26
  11. infrahub/config.py +9 -0
  12. infrahub/core/account.py +24 -47
  13. infrahub/core/attribute.py +53 -14
  14. infrahub/core/branch/models.py +8 -9
  15. infrahub/core/branch/tasks.py +0 -2
  16. infrahub/core/constants/infrahubkind.py +8 -0
  17. infrahub/core/constraint/node/runner.py +1 -1
  18. infrahub/core/convert_object_type/__init__.py +0 -0
  19. infrahub/core/convert_object_type/conversion.py +122 -0
  20. infrahub/core/convert_object_type/schema_mapping.py +56 -0
  21. infrahub/core/diff/calculator.py +65 -11
  22. infrahub/core/diff/combiner.py +38 -31
  23. infrahub/core/diff/coordinator.py +44 -28
  24. infrahub/core/diff/data_check_synchronizer.py +3 -2
  25. infrahub/core/diff/enricher/hierarchy.py +36 -27
  26. infrahub/core/diff/ipam_diff_parser.py +5 -4
  27. infrahub/core/diff/merger/merger.py +46 -16
  28. infrahub/core/diff/merger/serializer.py +1 -0
  29. infrahub/core/diff/model/field_specifiers_map.py +64 -0
  30. infrahub/core/diff/model/path.py +58 -58
  31. infrahub/core/diff/parent_node_adder.py +14 -16
  32. infrahub/core/diff/query/all_conflicts.py +1 -5
  33. infrahub/core/diff/query/artifact.py +10 -20
  34. infrahub/core/diff/query/diff_get.py +3 -6
  35. infrahub/core/diff/query/drop_nodes.py +42 -0
  36. infrahub/core/diff/query/field_specifiers.py +8 -7
  37. infrahub/core/diff/query/field_summary.py +2 -4
  38. infrahub/core/diff/query/filters.py +15 -1
  39. infrahub/core/diff/query/merge.py +284 -101
  40. infrahub/core/diff/query/save.py +26 -34
  41. infrahub/core/diff/query/summary_counts_enricher.py +34 -54
  42. infrahub/core/diff/query_parser.py +55 -65
  43. infrahub/core/diff/repository/deserializer.py +38 -24
  44. infrahub/core/diff/repository/repository.py +31 -12
  45. infrahub/core/diff/tasks.py +3 -3
  46. infrahub/core/graph/__init__.py +1 -1
  47. infrahub/core/manager.py +14 -11
  48. infrahub/core/migrations/graph/__init__.py +2 -0
  49. infrahub/core/migrations/graph/m003_relationship_parent_optional.py +1 -2
  50. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +2 -4
  51. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +11 -22
  52. infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -6
  53. infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +1 -2
  54. infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +1 -2
  55. infrahub/core/migrations/graph/m027_delete_isolated_nodes.py +50 -0
  56. infrahub/core/migrations/graph/m028_delete_diffs.py +38 -0
  57. infrahub/core/migrations/query/attribute_add.py +1 -2
  58. infrahub/core/migrations/query/attribute_rename.py +3 -6
  59. infrahub/core/migrations/query/delete_element_in_schema.py +3 -6
  60. infrahub/core/migrations/query/node_duplicate.py +3 -6
  61. infrahub/core/migrations/query/relationship_duplicate.py +3 -6
  62. infrahub/core/migrations/schema/node_attribute_remove.py +3 -6
  63. infrahub/core/migrations/schema/node_remove.py +3 -6
  64. infrahub/core/models.py +29 -2
  65. infrahub/core/node/__init__.py +18 -4
  66. infrahub/core/node/create.py +211 -0
  67. infrahub/core/protocols.py +51 -0
  68. infrahub/core/protocols_base.py +3 -0
  69. infrahub/core/query/__init__.py +2 -2
  70. infrahub/core/query/branch.py +27 -17
  71. infrahub/core/query/diff.py +186 -81
  72. infrahub/core/query/ipam.py +10 -20
  73. infrahub/core/query/node.py +65 -49
  74. infrahub/core/query/relationship.py +156 -58
  75. infrahub/core/query/resource_manager.py +1 -2
  76. infrahub/core/query/subquery.py +4 -6
  77. infrahub/core/relationship/model.py +4 -1
  78. infrahub/core/schema/__init__.py +2 -1
  79. infrahub/core/schema/attribute_parameters.py +36 -0
  80. infrahub/core/schema/attribute_schema.py +83 -8
  81. infrahub/core/schema/basenode_schema.py +25 -1
  82. infrahub/core/schema/definitions/core/__init__.py +21 -0
  83. infrahub/core/schema/definitions/internal.py +13 -3
  84. infrahub/core/schema/generated/attribute_schema.py +9 -3
  85. infrahub/core/schema/schema_branch.py +15 -7
  86. infrahub/core/validators/__init__.py +5 -1
  87. infrahub/core/validators/attribute/choices.py +1 -2
  88. infrahub/core/validators/attribute/enum.py +1 -2
  89. infrahub/core/validators/attribute/kind.py +1 -2
  90. infrahub/core/validators/attribute/length.py +13 -6
  91. infrahub/core/validators/attribute/optional.py +1 -2
  92. infrahub/core/validators/attribute/regex.py +5 -5
  93. infrahub/core/validators/attribute/unique.py +1 -3
  94. infrahub/core/validators/determiner.py +18 -2
  95. infrahub/core/validators/enum.py +7 -0
  96. infrahub/core/validators/node/hierarchy.py +3 -6
  97. infrahub/core/validators/query.py +1 -3
  98. infrahub/core/validators/relationship/count.py +6 -12
  99. infrahub/core/validators/relationship/optional.py +2 -4
  100. infrahub/core/validators/relationship/peer.py +3 -8
  101. infrahub/core/validators/tasks.py +1 -1
  102. infrahub/core/validators/uniqueness/query.py +12 -9
  103. infrahub/database/__init__.py +1 -3
  104. infrahub/events/group_action.py +1 -0
  105. infrahub/graphql/analyzer.py +139 -18
  106. infrahub/graphql/app.py +1 -1
  107. infrahub/graphql/loaders/node.py +1 -1
  108. infrahub/graphql/loaders/peers.py +1 -1
  109. infrahub/graphql/manager.py +4 -0
  110. infrahub/graphql/mutations/action.py +164 -0
  111. infrahub/graphql/mutations/convert_object_type.py +62 -0
  112. infrahub/graphql/mutations/main.py +24 -175
  113. infrahub/graphql/mutations/proposed_change.py +21 -18
  114. infrahub/graphql/queries/convert_object_type_mapping.py +36 -0
  115. infrahub/graphql/queries/diff/tree.py +2 -1
  116. infrahub/graphql/queries/relationship.py +1 -1
  117. infrahub/graphql/resolvers/many_relationship.py +4 -4
  118. infrahub/graphql/resolvers/resolver.py +4 -4
  119. infrahub/graphql/resolvers/single_relationship.py +2 -2
  120. infrahub/graphql/schema.py +6 -0
  121. infrahub/graphql/subscription/graphql_query.py +2 -2
  122. infrahub/graphql/types/branch.py +1 -1
  123. infrahub/menu/menu.py +31 -0
  124. infrahub/message_bus/messages/__init__.py +0 -10
  125. infrahub/message_bus/operations/__init__.py +0 -8
  126. infrahub/message_bus/operations/refresh/registry.py +1 -1
  127. infrahub/patch/queries/consolidate_duplicated_nodes.py +3 -6
  128. infrahub/patch/queries/delete_duplicated_edges.py +5 -10
  129. infrahub/prefect_server/models.py +1 -19
  130. infrahub/proposed_change/models.py +68 -3
  131. infrahub/proposed_change/tasks.py +907 -30
  132. infrahub/task_manager/models.py +10 -6
  133. infrahub/telemetry/database.py +1 -1
  134. infrahub/telemetry/tasks.py +1 -1
  135. infrahub/trigger/catalogue.py +2 -0
  136. infrahub/trigger/models.py +29 -3
  137. infrahub/trigger/setup.py +51 -15
  138. infrahub/trigger/tasks.py +4 -5
  139. infrahub/types.py +1 -1
  140. infrahub/webhook/models.py +2 -1
  141. infrahub/workflows/catalogue.py +85 -0
  142. infrahub/workflows/initialization.py +1 -3
  143. infrahub_sdk/timestamp.py +2 -2
  144. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/METADATA +4 -4
  145. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/RECORD +153 -146
  146. infrahub_testcontainers/container.py +0 -1
  147. infrahub_testcontainers/docker-compose.test.yml +4 -4
  148. infrahub_testcontainers/helpers.py +8 -2
  149. infrahub_testcontainers/performance_test.py +6 -3
  150. infrahub/message_bus/messages/check_generator_run.py +0 -26
  151. infrahub/message_bus/messages/finalize_validator_execution.py +0 -15
  152. infrahub/message_bus/messages/proposed_change/base_with_diff.py +0 -16
  153. infrahub/message_bus/messages/proposed_change/request_proposedchange_refreshartifacts.py +0 -11
  154. infrahub/message_bus/messages/request_generatordefinition_check.py +0 -20
  155. infrahub/message_bus/messages/request_proposedchange_pipeline.py +0 -23
  156. infrahub/message_bus/operations/check/__init__.py +0 -3
  157. infrahub/message_bus/operations/check/generator.py +0 -156
  158. infrahub/message_bus/operations/finalize/__init__.py +0 -3
  159. infrahub/message_bus/operations/finalize/validator.py +0 -133
  160. infrahub/message_bus/operations/requests/__init__.py +0 -9
  161. infrahub/message_bus/operations/requests/generator_definition.py +0 -140
  162. infrahub/message_bus/operations/requests/proposed_change.py +0 -629
  163. /infrahub/{message_bus/messages/proposed_change → actions}/__init__.py +0 -0
  164. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/LICENSE.txt +0 -0
  165. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/WHEEL +0 -0
  166. {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.3.0a0.dist-info}/entry_points.txt +0 -0
@@ -1,14 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Any
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Any, Generator
4
5
 
5
6
  from infrahub import config
6
- from infrahub.core.constants import GLOBAL_BRANCH_NAME, BranchSupportType
7
+ from infrahub.core.constants import GLOBAL_BRANCH_NAME, BranchSupportType, DiffAction, RelationshipStatus
7
8
  from infrahub.core.query import Query, QueryType
8
9
  from infrahub.core.timestamp import Timestamp
9
10
 
10
11
  if TYPE_CHECKING:
11
12
  from infrahub.core.branch import Branch
13
+ from infrahub.core.diff.model.field_specifiers_map import NodeFieldSpecifierMap
12
14
  from infrahub.database import InfrahubDatabase
13
15
 
14
16
 
@@ -106,8 +108,8 @@ class DiffCalculationQuery(DiffQuery):
106
108
  self,
107
109
  base_branch: Branch,
108
110
  diff_branch_from_time: Timestamp,
109
- current_node_field_specifiers: dict[str, set[str]] | None = None,
110
- new_node_field_specifiers: dict[str, set[str]] | None = None,
111
+ current_node_field_specifiers: NodeFieldSpecifierMap | None = None,
112
+ new_node_field_specifiers: NodeFieldSpecifierMap | None = None,
111
113
  **kwargs: Any,
112
114
  ):
113
115
  self.base_branch = base_branch
@@ -119,20 +121,20 @@ class DiffCalculationQuery(DiffQuery):
119
121
 
120
122
  previous_base_path_query = """
121
123
  WITH DISTINCT diff_path AS diff_path, has_more_data
122
- CALL {
123
- WITH diff_path
124
- WITH diff_path, nodes(diff_path) AS d_nodes, relationships(diff_path) AS d_rels
125
- WITH diff_path, d_rels[0] AS r_root, d_nodes[1] AS n, d_rels[1] AS r_node, d_nodes[2] AS attr_rel, d_rels[2] AS r_prop
124
+ CALL (diff_path) {
125
+ WITH nodes(diff_path) AS d_nodes, relationships(diff_path) AS d_rels
126
+ WITH d_rels[0] AS r_root, d_nodes[1] AS n, d_rels[1] AS r_node, d_nodes[2] AS attr_rel, d_rels[2] AS r_prop
126
127
  // -------------------------------------
127
128
  // add base branch paths before branched_from, if they exist
128
129
  // -------------------------------------
129
130
  WITH n, attr_rel, r_node, r_prop
131
+ // 'base_n' instead of 'n' here to get previous value for node with a migrated kind/inheritance
130
132
  OPTIONAL MATCH latest_base_path = (:Root)<-[base_r_root:IS_PART_OF {branch: $base_branch_name}]
131
- -(n)-[base_r_node {branch: $base_branch_name}]
133
+ -(base_n {uuid: n.uuid})-[base_r_node {branch: $base_branch_name}]
132
134
  -(attr_rel)-[base_r_prop {branch: $base_branch_name}]->(base_prop)
133
135
  WHERE type(base_r_node) = type(r_node)
134
136
  AND type(base_r_prop) = type(r_prop)
135
- AND [%(id_func)s(n), type(base_r_node)] <> [%(id_func)s(base_prop), type(base_r_prop)]
137
+ AND [%(id_func)s(base_n), type(base_r_node)] <> [%(id_func)s(base_prop), type(base_r_prop)]
136
138
  AND all(
137
139
  r in relationships(latest_base_path)
138
140
  WHERE r.from < $branch_from_time
@@ -142,7 +144,7 @@ CALL {
142
144
  // the migration leaves two nodes with the same UUID linked to the same Relationship
143
145
  // ------------------------
144
146
  AND (
145
- n.uuid IS NULL OR base_prop.uuid IS NULL OR n.uuid <> base_prop.uuid
147
+ base_n.uuid IS NULL OR base_prop.uuid IS NULL OR base_n.uuid <> base_prop.uuid
146
148
  OR type(base_r_node) <> "IS_RELATED" OR type(base_r_prop) <> "IS_RELATED"
147
149
  )
148
150
  WITH latest_base_path, base_r_root, base_r_node, base_r_prop
@@ -155,10 +157,9 @@ CALL {
155
157
  WITH diff_path, latest_base_path, has_more_data
156
158
  UNWIND [diff_path, latest_base_path] AS penultimate_path
157
159
  WITH DISTINCT penultimate_path, has_more_data
158
- CALL {
159
- WITH penultimate_path
160
- WITH penultimate_path, nodes(penultimate_path) AS d_nodes, relationships(penultimate_path) AS d_rels
161
- WITH penultimate_path, d_rels[0] AS r_root, d_nodes[1] AS n, d_rels[1] AS r_node, d_nodes[2] AS attr_rel, d_rels[2] AS r_prop
160
+ CALL (penultimate_path) {
161
+ WITH nodes(penultimate_path) AS d_nodes, relationships(penultimate_path) AS d_rels
162
+ WITH d_rels[0] AS r_root, d_nodes[1] AS n, d_rels[1] AS r_node, d_nodes[2] AS attr_rel, d_rels[2] AS r_prop
162
163
  // -------------------------------------
163
164
  // Add peer-side of any relationships to get the peer's ID
164
165
  // -------------------------------------
@@ -198,6 +199,13 @@ WITH reduce(
198
199
  diff_rel_paths = [], item IN [penultimate_path, peer_path] |
199
200
  CASE WHEN item IS NULL THEN diff_rel_paths ELSE diff_rel_paths + [item] END
200
201
  ) AS diff_rel_paths, has_more_data
202
+ // ------------------------
203
+ // make sure we still include has_more_data if diff_rel_paths is empty
204
+ // ------------------------
205
+ WITH CASE
206
+ WHEN diff_rel_paths = [] THEN [NULL]
207
+ ELSE diff_rel_paths
208
+ END AS diff_rel_paths, has_more_data
201
209
  """
202
210
 
203
211
  def get_previous_base_path_query(self, db: InfrahubDatabase) -> str:
@@ -231,10 +239,10 @@ class DiffNodePathsQuery(DiffCalculationQuery):
231
239
  self.params.update(params_dict)
232
240
  self.params.update(
233
241
  {
234
- "new_node_ids_list": list(self.new_node_field_specifiers.keys())
242
+ "new_node_ids_list": self.new_node_field_specifiers.get_uuids_list()
235
243
  if self.new_node_field_specifiers
236
244
  else None,
237
- "current_node_ids_list": list(self.current_node_field_specifiers.keys())
245
+ "current_node_ids_list": self.current_node_field_specifiers.get_uuids_list()
238
246
  if self.current_node_field_specifiers
239
247
  else None,
240
248
  }
@@ -276,7 +284,7 @@ WITH p, q, diff_rel, CASE
276
284
  WHEN $new_node_ids_list IS NOT NULL AND p.uuid IN $new_node_ids_list THEN $branch_from_time
277
285
  ELSE $from_time
278
286
  END AS row_from_time
279
- ORDER BY p.uuid DESC
287
+ ORDER BY %(id_func)s(p) DESC
280
288
  SKIP $offset
281
289
  LIMIT $limit
282
290
  // -------------------------------------
@@ -289,8 +297,7 @@ WITH one_result[0] AS p, one_result[1] AS q, one_result[2] AS diff_rel, one_resu
289
297
  // -------------------------------------
290
298
  // Exclude nodes added then removed on branch within timeframe
291
299
  // -------------------------------------
292
- CALL {
293
- WITH p, q, row_from_time
300
+ CALL (p, q, row_from_time) {
294
301
  OPTIONAL MATCH (q)<-[is_part_of:IS_PART_OF {branch: $branch_name}]-(p)
295
302
  WHERE row_from_time <= is_part_of.from < $to_time
296
303
  WITH DISTINCT is_part_of.status AS rel_status
@@ -302,8 +309,7 @@ WHERE intra_branch_update = FALSE
302
309
  // -------------------------------------
303
310
  // Get every path on this branch under each node
304
311
  // -------------------------------------
305
- CALL {
306
- WITH p, q, diff_rel, row_from_time
312
+ CALL (p, q, diff_rel, row_from_time) {
307
313
  OPTIONAL MATCH path = (
308
314
  (q)<-[top_diff_rel:IS_PART_OF]-(p)-[r_node]-(node)-[r_prop]-(prop)
309
315
  )
@@ -313,15 +319,15 @@ CALL {
313
319
  AND node.branch_support IN [$branch_aware, $branch_agnostic]
314
320
  AND type(r_prop) IN ["IS_VISIBLE", "IS_PROTECTED", "HAS_SOURCE", "HAS_OWNER", "HAS_VALUE", "IS_RELATED"]
315
321
  AND any(l in labels(prop) WHERE l in ["Boolean", "Node", "AttributeValue"])
316
- AND ALL(
317
- r in [r_node, r_prop]
318
- WHERE r.from < $to_time AND r.branch = top_diff_rel.branch
319
- )
320
322
  AND (top_diff_rel.to IS NULL OR top_diff_rel.to >= r_node.from)
321
323
  AND (r_node.to IS NULL OR r_node.to >= r_prop.from)
322
324
  AND [%(id_func)s(p), type(r_node)] <> [%(id_func)s(prop), type(r_prop)]
323
- AND top_diff_rel.status = r_node.status
324
- AND top_diff_rel.status = r_prop.status
325
+ AND r_node.from < $to_time
326
+ AND r_node.branch = top_diff_rel.branch
327
+ AND r_node.status = top_diff_rel.status
328
+ AND r_prop.from < $to_time
329
+ AND r_prop.branch = top_diff_rel.branch
330
+ AND r_prop.status = top_diff_rel.status
325
331
  // ------------------------
326
332
  // special handling for nodes that had their kind updated,
327
333
  // the migration leaves two nodes with the same UUID linked to the same Relationship
@@ -330,12 +336,11 @@ CALL {
330
336
  p.uuid IS NULL OR prop.uuid IS NULL OR p.uuid <> prop.uuid
331
337
  OR type(r_node) <> "IS_RELATED" OR type(r_prop) <> "IS_RELATED"
332
338
  )
333
- WITH path, p, node, prop, r_prop, r_node, type(r_node) AS rel_type, row_from_time
339
+ WITH path, node, prop, r_prop, r_node, type(r_node) AS rel_type, row_from_time
334
340
  // -------------------------------------
335
341
  // Exclude attributes/relationships added then removed on branch within timeframe
336
342
  // -------------------------------------
337
- CALL {
338
- WITH p, rel_type, node, row_from_time
343
+ CALL (p, rel_type, node, row_from_time) {
339
344
  OPTIONAL MATCH (p)-[rel_to_check {branch: $branch_name}]-(node)
340
345
  WHERE row_from_time <= rel_to_check.from < $to_time
341
346
  AND type(rel_to_check) = rel_type
@@ -371,15 +376,16 @@ class DiffFieldPathsQuery(DiffCalculationQuery):
371
376
 
372
377
  self.params.update(
373
378
  {
374
- "current_node_field_specifiers_map": {
375
- node_uuid: list(field_names)
376
- for node_uuid, field_names in self.current_node_field_specifiers.items()
377
- }
379
+ "current_node_ids_list": self.current_node_field_specifiers.get_uuids_list()
380
+ if self.current_node_field_specifiers
381
+ else None,
382
+ "new_node_ids_list": self.new_node_field_specifiers.get_uuids_list()
383
+ if self.new_node_field_specifiers
384
+ else None,
385
+ "current_node_field_specifiers_map": self.current_node_field_specifiers.get_uuid_field_names_map()
378
386
  if self.current_node_field_specifiers is not None
379
387
  else None,
380
- "new_node_field_specifiers_map": {
381
- node_uuid: list(field_names) for node_uuid, field_names in self.new_node_field_specifiers.items()
382
- }
388
+ "new_node_field_specifiers_map": self.new_node_field_specifiers.get_uuid_field_names_map()
383
389
  if self.new_node_field_specifiers is not None
384
390
  else None,
385
391
  }
@@ -400,16 +406,16 @@ AND (r_root.to IS NULL OR diff_rel.branch <> r_root.branch OR r_root.to >= diff_
400
406
  // node ID and field name filtering first pass
401
407
  AND (
402
408
  (
403
- $current_node_field_specifiers_map IS NOT NULL
404
- AND $current_node_field_specifiers_map[p.uuid] IS NOT NULL
409
+ $current_node_ids_list IS NOT NULL
410
+ AND p.uuid IN $current_node_ids_list
405
411
  AND q.name IN $current_node_field_specifiers_map[p.uuid]
406
412
  ) OR (
407
- $new_node_field_specifiers_map IS NOT NULL
408
- AND $new_node_field_specifiers_map[p.uuid] IS NOT NULL
413
+ $new_node_ids_list IS NOT NULL
414
+ AND p.uuid IN $new_node_ids_list
409
415
  AND q.name IN $new_node_field_specifiers_map[p.uuid]
410
416
  ) OR (
411
- $current_node_field_specifiers_map IS NULL
412
- AND $new_node_field_specifiers_map IS NULL
417
+ $new_node_ids_list IS NULL
418
+ AND $current_node_ids_list IS NULL
413
419
  )
414
420
  )
415
421
  // node ID and field name filtering second pass
@@ -417,8 +423,12 @@ AND (
417
423
  // time-based filters for nodes already included in the diff or fresh changes
418
424
  (
419
425
  (
420
- ($current_node_field_specifiers_map IS NOT NULL AND q.name IN $current_node_field_specifiers_map[p.uuid])
421
- OR ($current_node_field_specifiers_map IS NULL AND $new_node_field_specifiers_map IS NULL)
426
+ (
427
+ $current_node_ids_list IS NOT NULL
428
+ AND p.uuid IN $current_node_ids_list
429
+ AND q.name IN $current_node_field_specifiers_map[p.uuid]
430
+ )
431
+ OR ($current_node_ids_list IS NULL AND $new_node_ids_list IS NULL)
422
432
  )
423
433
  AND (r_root.from < $from_time OR p.branch_support = $branch_agnostic)
424
434
  AND (
@@ -428,7 +438,11 @@ AND (
428
438
  )
429
439
  // time-based filters for new nodes
430
440
  OR (
431
- ($new_node_field_specifiers_map IS NOT NULL AND q.name IN $new_node_field_specifiers_map[p.uuid])
441
+ (
442
+ $new_node_ids_list IS NOT NULL
443
+ AND p.uuid IN $new_node_ids_list
444
+ AND q.name IN $new_node_field_specifiers_map[p.uuid]
445
+ )
432
446
  AND (r_root.from < $branch_from_time OR p.branch_support = $branch_agnostic)
433
447
  AND (
434
448
  ($branch_from_time <= diff_rel.from < $to_time AND (diff_rel.to IS NULL OR diff_rel.to > $to_time))
@@ -454,15 +468,18 @@ WITH one_result[0] AS root, one_result[1] AS r_root, one_result[2] AS p, one_res
454
468
  // Add correct from_time for row
455
469
  // -------------------------------------
456
470
  WITH root, r_root, p, diff_rel, q, has_more_data, CASE
457
- WHEN $new_node_field_specifiers_map IS NOT NULL AND q.name IN $new_node_field_specifiers_map[p.uuid] THEN $branch_from_time
471
+ WHEN
472
+ $new_node_ids_list IS NOT NULL
473
+ AND p.uuid IN $new_node_ids_list
474
+ AND q.name IN $new_node_field_specifiers_map[p.uuid]
475
+ THEN $branch_from_time
458
476
  ELSE $from_time
459
477
  END AS row_from_time
460
478
  // -------------------------------------
461
479
  // Exclude attributes/relationship under nodes deleted on this branch in the timeframe
462
480
  // because those were all handled above at the node level
463
481
  // -------------------------------------
464
- CALL {
465
- WITH root, p, row_from_time
482
+ CALL (root, p, row_from_time) {
466
483
  OPTIONAL MATCH (root)<-[r_root_deleted:IS_PART_OF {branch: $branch_name}]-(p)
467
484
  WHERE row_from_time <= r_root_deleted.from < $to_time
468
485
  WITH r_root_deleted
@@ -476,8 +493,7 @@ WHERE node_deleted = FALSE
476
493
  // Exclude relationships added and deleted within the timeframe
477
494
  // -------------------------------------
478
495
  WITH root, r_root, p, diff_rel, q, has_more_data, row_from_time, type(diff_rel) AS rel_type
479
- CALL {
480
- WITH p, rel_type, q, row_from_time
496
+ CALL (p, rel_type, q, row_from_time) {
481
497
  OPTIONAL MATCH (p)-[rel_to_check {branch: $branch_name}]-(q)
482
498
  WHERE row_from_time <= rel_to_check.from < $to_time
483
499
  AND type(rel_to_check) = rel_type
@@ -490,8 +506,7 @@ WHERE intra_branch_update = FALSE
490
506
  // -------------------------------------
491
507
  // Get every path on this branch under each attribute/relationship
492
508
  // -------------------------------------
493
- CALL {
494
- WITH root, r_root, p, diff_rel, q
509
+ CALL (root, r_root, p, diff_rel, q) {
495
510
  OPTIONAL MATCH path = (
496
511
  (root:Root)<-[mid_r_root:IS_PART_OF]-(p)-[mid_diff_rel]-(q)-[r_prop]-(prop)
497
512
  )
@@ -526,8 +541,7 @@ CALL {
526
541
  // Exclude properties added and deleted within the timeframe
527
542
  // -------------------------------------
528
543
  WITH q, nodes(latest_prop_path)[3] AS prop, type(relationships(latest_prop_path)[2]) AS rel_type, latest_prop_path, has_more_data, row_from_time
529
- CALL {
530
- WITH q, rel_type, prop, row_from_time
544
+ CALL (q, rel_type, prop, row_from_time) {
531
545
  OPTIONAL MATCH (q)-[rel_to_check {branch: $branch_name}]-(prop)
532
546
  WHERE row_from_time <= rel_to_check.from < $to_time
533
547
  AND type(rel_to_check) = rel_type
@@ -554,15 +568,16 @@ class DiffPropertyPathsQuery(DiffCalculationQuery):
554
568
 
555
569
  self.params.update(
556
570
  {
557
- "current_node_field_specifiers_map": {
558
- node_uuid: list(field_names)
559
- for node_uuid, field_names in self.current_node_field_specifiers.items()
560
- }
571
+ "current_node_ids_list": self.current_node_field_specifiers.get_uuids_list()
572
+ if self.current_node_field_specifiers
573
+ else None,
574
+ "new_node_ids_list": self.new_node_field_specifiers.get_uuids_list()
575
+ if self.new_node_field_specifiers
576
+ else None,
577
+ "current_node_field_specifiers_map": self.current_node_field_specifiers.get_uuid_field_names_map()
561
578
  if self.current_node_field_specifiers is not None
562
579
  else None,
563
- "new_node_field_specifiers_map": {
564
- node_uuid: list(field_names) for node_uuid, field_names in self.new_node_field_specifiers.items()
565
- }
580
+ "new_node_field_specifiers_map": self.new_node_field_specifiers.get_uuid_field_names_map()
566
581
  if self.new_node_field_specifiers is not None
567
582
  else None,
568
583
  }
@@ -580,16 +595,16 @@ AND type(r_node) IN ["HAS_ATTRIBUTE", "IS_RELATED"]
580
595
  // node ID and field name filtering first pass
581
596
  AND (
582
597
  (
583
- $current_node_field_specifiers_map IS NOT NULL
584
- AND $current_node_field_specifiers_map[n.uuid] IS NOT NULL
598
+ $current_node_ids_list IS NOT NULL
599
+ AND n.uuid IN $current_node_ids_list
585
600
  AND p.name IN $current_node_field_specifiers_map[n.uuid]
586
601
  ) OR (
587
- $new_node_field_specifiers_map IS NOT NULL
588
- AND $new_node_field_specifiers_map[n.uuid] IS NOT NULL
602
+ $new_node_ids_list IS NOT NULL
603
+ AND n.uuid IN $new_node_ids_list
589
604
  AND p.name IN $new_node_field_specifiers_map[n.uuid]
590
605
  ) OR (
591
- $current_node_field_specifiers_map IS NULL
592
- AND $new_node_field_specifiers_map IS NULL
606
+ $new_node_ids_list IS NULL
607
+ AND $current_node_ids_list IS NULL
593
608
  )
594
609
  )
595
610
  // node ID and field name filtering second pass
@@ -597,8 +612,12 @@ AND (
597
612
  // time-based filters for nodes already included in the diff or fresh changes
598
613
  (
599
614
  (
600
- ($current_node_field_specifiers_map IS NOT NULL AND p.name IN $current_node_field_specifiers_map[n.uuid])
601
- OR ($current_node_field_specifiers_map IS NULL AND $new_node_field_specifiers_map IS NULL)
615
+ (
616
+ $current_node_ids_list IS NOT NULL
617
+ AND n.uuid IN $current_node_ids_list
618
+ AND p.name IN $current_node_field_specifiers_map[n.uuid]
619
+ )
620
+ OR ($current_node_ids_list IS NULL AND $new_node_ids_list IS NULL)
602
621
  )
603
622
  AND (
604
623
  ($from_time <= diff_rel.from < $to_time AND (diff_rel.to IS NULL OR diff_rel.to > $to_time))
@@ -612,7 +631,11 @@ AND (
612
631
  )
613
632
  // time-based filters for new nodes
614
633
  OR (
615
- ($new_node_field_specifiers_map IS NOT NULL AND p.name IN $new_node_field_specifiers_map[n.uuid])
634
+ (
635
+ $new_node_ids_list IS NOT NULL
636
+ AND n.uuid IN $new_node_ids_list
637
+ AND p.name IN $new_node_field_specifiers_map[n.uuid]
638
+ )
616
639
  AND (
617
640
  ($branch_from_time <= diff_rel.from < $to_time AND (diff_rel.to IS NULL OR diff_rel.to > $to_time))
618
641
  OR ($branch_from_time <= diff_rel.to < $to_time)
@@ -667,7 +690,11 @@ WITH one_result[0] AS diff_rel_path, one_result[1] AS r_root, one_result[2] AS n
667
690
  // Add correct from_time for row
668
691
  // -------------------------------------
669
692
  WITH diff_rel_path, r_root, n, r_node, p, diff_rel, has_more_data, CASE
670
- WHEN $new_node_field_specifiers_map IS NOT NULL AND p.name IN $new_node_field_specifiers_map[n.uuid] THEN $branch_from_time
693
+ WHEN
694
+ $new_node_ids_list IS NOT NULL
695
+ AND n.uuid IN $new_node_ids_list
696
+ AND p.name IN $new_node_field_specifiers_map[n.uuid]
697
+ THEN $branch_from_time
671
698
  ELSE $from_time
672
699
  END AS row_from_time
673
700
  WITH diff_rel_path, r_root, n, r_node, p, diff_rel, has_more_data, row_from_time
@@ -681,24 +708,21 @@ ORDER BY
681
708
  r_node.from DESC,
682
709
  r_root.from DESC
683
710
  WITH n, p, row_from_time, diff_rel, diff_rel_path, has_more_data
684
- CALL {
711
+ CALL (n, p, row_from_time){
685
712
  // -------------------------------------
686
713
  // Exclude properties under nodes and attributes/relationships deleted
687
714
  // on this branch in the timeframe because those were all handled above
688
715
  // -------------------------------------
689
- WITH n, p, row_from_time
690
- CALL {
691
- WITH n, row_from_time
716
+ CALL (n, row_from_time) {
692
717
  OPTIONAL MATCH (root:Root)<-[r_root_deleted:IS_PART_OF {branch: $branch_name}]-(n)
693
- WHERE row_from_time <= r_root_deleted.from < $to_time
718
+ WHERE r_root_deleted.from < $to_time
694
719
  WITH r_root_deleted
695
720
  ORDER BY r_root_deleted.status DESC
696
721
  LIMIT 1
697
722
  RETURN COALESCE(r_root_deleted.status = "deleted", FALSE) AS node_deleted
698
723
  }
699
- WITH n, p, row_from_time, node_deleted
700
- CALL {
701
- WITH n, p, row_from_time
724
+ WITH node_deleted
725
+ CALL (n, p, row_from_time) {
702
726
  OPTIONAL MATCH (n)-[r_node_deleted {branch: $branch_name}]-(p)
703
727
  WHERE row_from_time <= r_node_deleted.from < $to_time
704
728
  AND type(r_node_deleted) IN ["HAS_ATTRIBUTE", "IS_RELATED"]
@@ -718,3 +742,84 @@ WITH n, p, type(diff_rel) AS drt, head(collect(diff_rel_path)) AS diff_path, has
718
742
  self.add_to_query(self.get_relationship_peer_side_query(db=db))
719
743
  self.add_to_query("UNWIND diff_rel_paths AS diff_path")
720
744
  self.return_labels = ["DISTINCT diff_path AS diff_path", "has_more_data"]
745
+
746
+
747
+ @dataclass
748
+ class MigratedKindNode:
749
+ uuid: str
750
+ kind: str
751
+ db_id: str
752
+ from_time: Timestamp
753
+ action: DiffAction
754
+ has_more_data: bool
755
+
756
+
757
+ class DiffMigratedKindNodesQuery(DiffCalculationQuery):
758
+ name = "diff_migrated_kind_nodes_query"
759
+
760
+ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
761
+ params_dict = self.get_params()
762
+ self.params.update(params_dict)
763
+ migrated_kind_nodes_query = """
764
+ // -------------------------------------
765
+ // Identify nodes added/removed on branch in the time frame
766
+ // -------------------------------------
767
+ MATCH (:Root)<-[diff_rel:IS_PART_OF {branch: $branch_name}]-(n:Node)
768
+ WHERE (
769
+ ($from_time <= diff_rel.from < $to_time AND (diff_rel.to IS NULL OR diff_rel.to > $to_time))
770
+ OR ($from_time <= diff_rel.to < $to_time)
771
+ )
772
+ AND n.branch_support = $branch_aware
773
+ WITH DISTINCT n.uuid AS node_uuid, %(id_func)s(n) AS db_id
774
+ WITH node_uuid, count(*) AS num_nodes_with_uuid
775
+ WHERE num_nodes_with_uuid > 1
776
+ // -------------------------------------
777
+ // Limit the number of nodes
778
+ // -------------------------------------
779
+ WITH node_uuid
780
+ ORDER BY node_uuid
781
+ SKIP $offset
782
+ LIMIT $limit
783
+ WITH collect(node_uuid) AS node_uuids
784
+ WITH node_uuids, size(node_uuids) = $limit AS has_more_data
785
+ MATCH (:Root)<-[diff_rel:IS_PART_OF {branch: $branch_name}]-(n:Node)
786
+ WHERE n.uuid IN node_uuids
787
+ AND (
788
+ ($from_time <= diff_rel.from < $to_time AND (diff_rel.to IS NULL OR diff_rel.to > $to_time))
789
+ OR ($from_time <= diff_rel.to < $to_time)
790
+ )
791
+ // -------------------------------------
792
+ // Ignore node created and deleted on this branch
793
+ // -------------------------------------
794
+ CALL (n) {
795
+ OPTIONAL MATCH (:Root)<-[diff_rel:IS_PART_OF {branch: $branch_name}]-(n)
796
+ WITH diff_rel
797
+ ORDER BY diff_rel.from ASC
798
+ WITH collect(diff_rel.status) AS statuses
799
+ RETURN statuses = ["active", "deleted"] AS intra_branch_update
800
+ }
801
+ WITH n.uuid AS uuid, n.kind AS kind, %(id_func)s(n) AS db_id, diff_rel.from_time AS from_time, diff_rel.status AS status, has_more_data
802
+ WHERE intra_branch_update = FALSE
803
+ """ % {"id_func": db.get_id_function_name()}
804
+ self.add_to_query(query=migrated_kind_nodes_query)
805
+ self.return_labels = [
806
+ "uuid",
807
+ "kind",
808
+ "db_id",
809
+ "from_time",
810
+ "status",
811
+ "has_more_data",
812
+ ]
813
+
814
+ def get_migrated_kind_nodes(self) -> Generator[MigratedKindNode, None, None]:
815
+ for result in self.get_results():
816
+ yield MigratedKindNode(
817
+ uuid=result.get_as_type("uuid", return_type=str),
818
+ kind=result.get_as_type("kind", return_type=str),
819
+ db_id=result.get_as_type("db_id", return_type=str),
820
+ from_time=result.get_as_type("from_time", return_type=Timestamp),
821
+ action=DiffAction.REMOVED
822
+ if result.get_as_type("status", return_type=str).lower() == RelationshipStatus.DELETED.value
823
+ else DiffAction.ADDED,
824
+ has_more_data=result.get_as_type("has_more_data", bool),
825
+ )
@@ -80,8 +80,7 @@ class IPPrefixSubnetFetch(Query):
80
80
  // First match on IPNAMESPACE
81
81
  MATCH (ns:%(ns_label)s)
82
82
  WHERE ns.uuid = $ns_id
83
- CALL {
84
- WITH ns
83
+ CALL (ns) {
85
84
  MATCH (ns)-[r:IS_PART_OF]-(root:Root)
86
85
  WHERE %(branch_filter)s
87
86
  RETURN ns as ns1, r as r1
@@ -104,8 +103,7 @@ class IPPrefixSubnetFetch(Query):
104
103
  // ---
105
104
  // FIND ALL CHILDREN OF THESE PREFIXES
106
105
  // ---
107
- CALL {
108
- WITH all_prefixes
106
+ CALL (all_prefixes) {
109
107
  UNWIND all_prefixes as prefix
110
108
  OPTIONAL MATCH (prefix)<-[:IS_RELATED]-(ch_rel:Relationship)<-[:IS_RELATED]-(children:BuiltinIPPrefix)
111
109
  WHERE ch_rel.name = "parent__child"
@@ -171,8 +169,7 @@ class IPPrefixIPAddressFetch(Query):
171
169
  // First match on IPNAMESPACE
172
170
  MATCH (ns:%(ns_label)s)
173
171
  WHERE ns.uuid = $ns_id
174
- CALL {
175
- WITH ns
172
+ CALL (ns) {
176
173
  MATCH (ns)-[r:IS_PART_OF]-(root:Root)
177
174
  WHERE %(branch_filter)s
178
175
  RETURN ns as ns1, r as r1
@@ -271,8 +268,7 @@ class IPPrefixUtilization(Query):
271
268
  query = f"""
272
269
  MATCH (pfx:Node)
273
270
  WHERE pfx.uuid IN $ids
274
- CALL {{
275
- WITH pfx
271
+ CALL (pfx) {{
276
272
  MATCH (pfx)-[r_rel1:IS_RELATED]-(rl:Relationship)<-[r_rel2:IS_RELATED]-(child:Node)
277
273
  WHERE rl.name IN [{", ".join(self.allocated_kinds_rel)}]
278
274
  AND any(l IN labels(child) WHERE l IN [{", ".join(self.allocated_kinds)}])
@@ -425,8 +421,7 @@ class IPPrefixReconcileQuery(Query):
425
421
  // ------------------
426
422
  // Get prefix node's current parent, if it exists
427
423
  // ------------------
428
- CALL {
429
- WITH ip_node
424
+ CALL (ip_node) {
430
425
  OPTIONAL MATCH parent_prefix_path = (ip_node)-[r1:IS_RELATED]->(:Relationship {name: "parent__child"})-[r2:IS_RELATED]->(current_parent:%(ip_prefix_kind)s)
431
426
  WHERE all(r IN relationships(parent_prefix_path) WHERE (%(branch_filter)s))
432
427
  RETURN current_parent, (r1.status = "active" AND r2.status = "active") AS parent_is_active
@@ -444,8 +439,7 @@ class IPPrefixReconcileQuery(Query):
444
439
  // ------------------
445
440
  // Get prefix node's current prefix children, if any exist
446
441
  // ------------------
447
- CALL {
448
- WITH ip_node
442
+ CALL (ip_node) {
449
443
  OPTIONAL MATCH child_prefix_path = (ip_node)<-[r1:IS_RELATED]-(:Relationship {name: "parent__child"})<-[r2:IS_RELATED]-(current_prefix_child:%(ip_prefix_kind)s)
450
444
  WHERE all(r IN relationships(child_prefix_path) WHERE (%(branch_filter)s))
451
445
  WITH current_prefix_child, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -457,8 +451,7 @@ class IPPrefixReconcileQuery(Query):
457
451
  // ------------------
458
452
  // Get prefix node's current address children, if any exist
459
453
  // ------------------
460
- CALL {
461
- WITH ip_node
454
+ CALL (ip_node) {
462
455
  OPTIONAL MATCH child_address_path = (ip_node)-[r1:IS_RELATED]-(:Relationship {name: "ip_prefix__ip_address"})-[r2:IS_RELATED]-(current_address_child:%(ip_address_kind)s)
463
456
  WHERE all(r IN relationships(child_address_path) WHERE (%(branch_filter)s))
464
457
  WITH current_address_child, (r1.status = "active" AND r2.status = "active") AS is_active
@@ -480,8 +473,7 @@ class IPPrefixReconcileQuery(Query):
480
473
  // ------------------
481
474
  // Identify the correct parent, if any, for the prefix node
482
475
  // ------------------
483
- CALL {
484
- WITH ip_namespace
476
+ CALL (ip_namespace) {
485
477
  OPTIONAL MATCH parent_path = (ip_namespace)-[pr1:IS_RELATED {status: "active"}]-(ns_rel:Relationship {name: "ip_namespace__ip_prefix"})
486
478
  -[pr2:IS_RELATED {status: "active"}]-(maybe_new_parent:%(ip_prefix_kind)s)
487
479
  -[har:HAS_ATTRIBUTE]->(:Attribute {name: "prefix"})
@@ -517,9 +509,8 @@ class IPPrefixReconcileQuery(Query):
517
509
  // ------------------
518
510
  // Identify the correct children, if any, for the prefix node
519
511
  // ------------------
520
- CALL {
512
+ CALL (ip_namespace, ip_node) {
521
513
  // Get ALL possible children for the prefix node
522
- WITH ip_namespace, ip_node
523
514
  OPTIONAL MATCH child_path = (
524
515
  (ip_namespace)-[r1:IS_RELATED]
525
516
  -(ns_rel:Relationship)-[r2:IS_RELATED]
@@ -558,12 +549,11 @@ class IPPrefixReconcileQuery(Query):
558
549
  WITH ip_namespace, ip_node, current_parent, current_children, new_parent, collect([maybe_new_child, latest_mnc_attribute]) AS maybe_children_ips
559
550
  WITH ip_namespace, ip_node, current_parent, current_children, new_parent, maybe_children_ips, range(0, size(maybe_children_ips) - 1) AS child_indices
560
551
  UNWIND child_indices as ind
561
- CALL {
552
+ CALL (ind, maybe_children_ips) {
562
553
  // ------------------
563
554
  // Filter all possible children to remove those that have a more-specific parent
564
555
  // among the list of all possible children
565
556
  // ------------------
566
- WITH ind, maybe_children_ips
567
557
  WITH ind, maybe_children_ips AS ips
568
558
  RETURN REDUCE(
569
559
  has_more_specific_parent = FALSE, potential_parent IN ips |