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
@@ -30,8 +30,7 @@ class EnrichedDiffRootsUpsertQuery(Query):
30
30
  query = """
31
31
  UNWIND $diff_root_list AS diff_root_map
32
32
  WITH diff_root_map
33
- CALL {
34
- WITH diff_root_map
33
+ CALL (diff_root_map) {
35
34
  MERGE (diff_root:DiffRoot {uuid: diff_root_map.uuid})
36
35
  SET diff_root.base_branch = diff_root_map.base_branch
37
36
  SET diff_root.diff_branch = diff_root_map.diff_branch
@@ -43,8 +42,7 @@ CALL {
43
42
  WITH DISTINCT diff_root AS diff_root
44
43
  WITH collect(diff_root) AS diff_roots
45
44
  WHERE SIZE(diff_roots) = 2
46
- CALL {
47
- WITH diff_roots
45
+ CALL (diff_roots) {
48
46
  WITH diff_roots[0] AS base_diff_node, diff_roots[1] AS branch_diff_node
49
47
  MERGE (base_diff_node)-[:DIFF_HAS_PARTNER]-(branch_diff_node)
50
48
  SET (base_diff_node).partner_uuid = (branch_diff_node).uuid
@@ -85,18 +83,20 @@ UNWIND $node_details_list AS node_details
85
83
  WITH
86
84
  node_details.root_uuid AS root_uuid,
87
85
  node_details.node_map AS node_map,
88
- toString(node_details.node_map.node_properties.uuid) AS node_uuid
86
+ toString(node_details.node_map.node_properties.uuid) AS node_uuid,
87
+ node_details.node_map.node_properties.db_id AS node_db_id
89
88
  MERGE (diff_root:DiffRoot {uuid: root_uuid})
90
- MERGE (diff_root)-[:DIFF_HAS_NODE]->(diff_node:DiffNode {uuid: node_uuid})
89
+ MERGE (diff_root)-[:DIFF_HAS_NODE]->(diff_node:DiffNode {uuid: node_uuid, db_id: node_db_id})
91
90
  WITH root_uuid, node_map, diff_node, (node_map.conflict_params IS NOT NULL) AS has_node_conflict
92
91
  SET
93
92
  diff_node.kind = node_map.node_properties.kind,
94
93
  diff_node.label = node_map.node_properties.label,
95
94
  diff_node.changed_at = node_map.node_properties.changed_at,
96
95
  diff_node.action = node_map.node_properties.action,
96
+ diff_node.is_node_kind_migration = node_map.node_properties.is_node_kind_migration,
97
97
  diff_node.path_identifier = node_map.node_properties.path_identifier
98
98
  WITH root_uuid, node_map, diff_node, has_node_conflict
99
- CALL {
99
+ CALL (diff_node) {
100
100
  // -------------------------
101
101
  // delete parent-child relationships for included nodes, they will be added in EnrichedNodesLinkQuery
102
102
  // -------------------------
@@ -105,43 +105,38 @@ CALL {
105
105
  DELETE parent_rel
106
106
  }
107
107
  OPTIONAL MATCH (diff_node)-[:DIFF_HAS_CONFLICT]->(current_node_conflict:DiffConflict)
108
- CALL {
108
+ CALL (diff_node, current_node_conflict, has_node_conflict) {
109
109
  // -------------------------
110
110
  // create a node-level conflict, if necessary
111
111
  // -------------------------
112
112
  WITH diff_node, current_node_conflict, has_node_conflict
113
- WITH diff_node, current_node_conflict, has_node_conflict
114
113
  WHERE current_node_conflict IS NULL AND has_node_conflict = TRUE
115
114
  CREATE (diff_node)-[:DIFF_HAS_CONFLICT]->(:DiffConflict)
116
115
  }
117
- CALL {
116
+ CALL (current_node_conflict, has_node_conflict) {
118
117
  // -------------------------
119
118
  // delete a node-level conflict, if necessary
120
119
  // -------------------------
121
120
  WITH current_node_conflict, has_node_conflict
122
- WITH current_node_conflict, has_node_conflict
123
121
  WHERE current_node_conflict IS NOT NULL AND has_node_conflict = FALSE
124
122
  DETACH DELETE current_node_conflict
125
123
  }
126
124
  WITH root_uuid, node_map, diff_node, has_node_conflict, node_map.conflict_params AS node_conflict_params
127
- CALL {
125
+ CALL (diff_node, has_node_conflict, node_conflict_params) {
128
126
  // -------------------------
129
127
  // set the properties of the node-level conflict, if necessary
130
128
  // -------------------------
131
129
  WITH diff_node, has_node_conflict, node_conflict_params
132
- WITH diff_node, has_node_conflict, node_conflict_params
133
130
  WHERE has_node_conflict = TRUE
134
131
  OPTIONAL MATCH (diff_node)-[:DIFF_HAS_CONFLICT]->(node_conflict:DiffConflict)
135
132
  SET node_conflict = node_conflict_params
136
133
  }
137
- CALL {
134
+ CALL (diff_node, node_map) {
138
135
  // -------------------------
139
136
  // remove stale attributes for this node
140
137
  // -------------------------
141
- WITH diff_node, node_map
142
- CALL {
143
- WITH diff_node, node_map
144
- WITH diff_node, %(attr_name_list_comp)s AS attr_names
138
+ CALL (diff_node, node_map) {
139
+ WITH %(attr_name_list_comp)s AS attr_names
145
140
  OPTIONAL MATCH (diff_node)-[:DIFF_HAS_ATTRIBUTE]->(attr_to_delete:DiffAttribute)
146
141
  WHERE NOT (attr_to_delete.name IN attr_names)
147
142
  OPTIONAL MATCH (attr_to_delete)-[*..6]->(next_to_delete)
@@ -161,9 +156,8 @@ CALL {
161
156
  // -------------------------
162
157
  // remove stale properties for this attribute
163
158
  // -------------------------
164
- CALL {
165
- WITH diff_attribute, node_attribute
166
- WITH diff_attribute, %(attr_props_list_comp)s AS prop_types
159
+ CALL (diff_attribute, node_attribute) {
160
+ WITH %(attr_props_list_comp)s AS prop_types
167
161
  OPTIONAL MATCH (diff_attribute)-[:DIFF_HAS_PROPERTY]->(prop_to_delete:DiffProperty)
168
162
  WHERE NOT (prop_to_delete.property_type IN prop_types)
169
163
  OPTIONAL MATCH (prop_to_delete)-[*..4]->(next_to_delete)
@@ -190,9 +184,8 @@ CALL {
190
184
  // -------------------------
191
185
  // remove stale relationships for this node
192
186
  // -------------------------
193
- CALL {
194
- WITH diff_node, node_map
195
- WITH diff_node, %(rel_name_list_comp)s AS rel_names
187
+ CALL (diff_node, node_map) {
188
+ WITH %(rel_name_list_comp)s AS rel_names
196
189
  OPTIONAL MATCH (diff_node)-[:DIFF_HAS_RELATIONSHIP]->(rel_to_delete:DiffRelationship)
197
190
  WHERE NOT (rel_to_delete.name IN rel_names)
198
191
  OPTIONAL MATCH (rel_to_delete)-[*..8]->(next_to_delete)
@@ -210,9 +203,8 @@ SET diff_relationship = node_relationship.node_properties
210
203
  // remove stale elements for this relationship group
211
204
  // -------------------------
212
205
  WITH diff_relationship, node_relationship
213
- CALL {
214
- WITH diff_relationship, node_relationship
215
- WITH diff_relationship, %(rel_peers_list_comp)s AS rel_peers
206
+ CALL (diff_relationship, node_relationship) {
207
+ WITH %(rel_peers_list_comp)s AS rel_peers
216
208
  OPTIONAL MATCH (diff_relationship)-[:DIFF_HAS_ELEMENT]->(element_to_delete:DiffRelationshipElement)
217
209
  WHERE NOT (element_to_delete.peer_id IN rel_peers)
218
210
  OPTIONAL MATCH (element_to_delete)-[*..6]->(next_to_delete)
@@ -245,9 +237,8 @@ FOREACH (i in CASE WHEN has_element_conflict = TRUE THEN [1] ELSE [] END |
245
237
  // remove stale properties for this relationship element
246
238
  // -------------------------
247
239
  WITH diff_relationship_element, node_single_relationship
248
- CALL {
249
- WITH diff_relationship_element, node_single_relationship
250
- WITH diff_relationship_element, %(element_props_list_comp)s AS element_props
240
+ CALL (diff_relationship_element, node_single_relationship) {
241
+ WITH %(element_props_list_comp)s AS element_props
251
242
  OPTIONAL MATCH (diff_relationship_element)-[:DIFF_HAS_PROPERTY]->(property_to_delete:DiffProperty)
252
243
  WHERE NOT (property_to_delete.property_type IN element_props)
253
244
  OPTIONAL MATCH (property_to_delete)-[*..4]->(next_to_delete)
@@ -401,6 +392,8 @@ FOREACH (i in CASE WHEN has_property_conflict = TRUE THEN [1] ELSE [] END |
401
392
  "node_properties": {
402
393
  "uuid": enriched_node.uuid,
403
394
  "kind": enriched_node.kind,
395
+ "db_id": enriched_node.identifier.db_id,
396
+ "is_node_kind_migration": enriched_node.is_node_kind_migration,
404
397
  "label": enriched_node.label,
405
398
  "changed_at": enriched_node.changed_at.to_string() if enriched_node.changed_at else None,
406
399
  "action": enriched_node.action.value,
@@ -447,10 +440,9 @@ WITH keys($parent_node_map) AS child_node_uuids
447
440
  MATCH (diff_root:DiffRoot {uuid: $root_uuid})
448
441
  MATCH (diff_root)-[:DIFF_HAS_NODE]->(child_node:DiffNode)
449
442
  WHERE child_node.uuid IN child_node_uuids
450
- CALL {
451
- WITH diff_root, child_node
452
- WITH diff_root, child_node, $parent_node_map[child_node.uuid] AS sub_map
453
- WITH diff_root, child_node, sub_map, keys(sub_map) AS relationship_names
443
+ CALL (diff_root, child_node) {
444
+ WITH $parent_node_map[child_node.uuid] AS sub_map
445
+ WITH sub_map, keys(sub_map) AS relationship_names
454
446
  MATCH (child_node)-[:DIFF_HAS_RELATIONSHIP]->(diff_rel_group:DiffRelationship)
455
447
  WHERE diff_rel_group.name IN relationship_names
456
448
  WITH diff_root, diff_rel_group, toString(sub_map[diff_rel_group.name]) AS parent_uuid
@@ -46,70 +46,59 @@ WHERE ($diff_id IS NOT NULL AND root.uuid = $diff_id)
46
46
  OR ($tracking_id IS NOT NULL AND root.tracking_id = $tracking_id AND root.diff_branch = $diff_branch_name)
47
47
  MATCH (root)-[:DIFF_HAS_NODE]->(dn:DiffNode)
48
48
  WHERE $node_uuids IS NULL OR dn.uuid IN $node_uuids
49
- CALL {
49
+ CALL (dn) {
50
50
  // ----------------------
51
51
  // handle attribute count updates
52
52
  // ----------------------
53
- WITH dn
54
53
  MATCH (dn)-[:DIFF_HAS_ATTRIBUTE]->(da:DiffAttribute)
55
- CALL {
56
- WITH da
54
+ CALL (da) {
57
55
  OPTIONAL MATCH (da)-[:DIFF_HAS_PROPERTY]->(dp:DiffProperty)-[:DIFF_HAS_CONFLICT]->(dc:DiffConflict)
58
- WITH da, count(dc) AS num_conflicts
56
+ WITH count(dc) AS num_conflicts
59
57
  SET da.num_conflicts = num_conflicts
60
58
  SET da.contains_conflict = (num_conflicts > 0)
61
59
  }
62
- CALL {
63
- WITH da
60
+ CALL (da) {
64
61
  OPTIONAL MATCH (da)-[:DIFF_HAS_PROPERTY]->(dp:DiffProperty {action: "added"})
65
- WITH da, count(dp.action) AS num_added
62
+ WITH count(dp.action) AS num_added
66
63
  SET da.num_added = num_added
67
64
  }
68
- CALL {
69
- WITH da
65
+ CALL (da) {
70
66
  OPTIONAL MATCH (da)-[:DIFF_HAS_PROPERTY]->(dp:DiffProperty {action: "updated"})
71
- WITH da, count(dp.action) AS num_updated
67
+ WITH count(dp.action) AS num_updated
72
68
  SET da.num_updated = num_updated
73
69
  }
74
- CALL {
75
- WITH da
70
+ CALL (da) {
76
71
  OPTIONAL MATCH (da)-[:DIFF_HAS_PROPERTY]->(dp:DiffProperty {action: "removed"})
77
- WITH da, count(dp.action) AS num_removed
72
+ WITH count(dp.action) AS num_removed
78
73
  SET da.num_removed = num_removed
79
74
  }
80
75
  }
81
- CALL {
82
- WITH dn
76
+ CALL (dn) {
83
77
  MATCH (dn)-[:DIFF_HAS_RELATIONSHIP]->(dr:DiffRelationship)
84
- CALL {
78
+ CALL (dr) {
85
79
  // ----------------------
86
80
  // handle relationship element count updates
87
81
  // ----------------------
88
- WITH dr
89
82
  MATCH (dr)-[:DIFF_HAS_ELEMENT]->(dre:DiffRelationshipElement)
90
- CALL {
91
- WITH dre
83
+ CALL (dre) {
92
84
  OPTIONAL MATCH (dre)-[*..4]->(dc:DiffConflict)
93
- WITH dre, count(dc) AS num_conflicts
85
+ WITH count(dc) AS num_conflicts
94
86
  SET dre.num_conflicts = num_conflicts
95
87
  SET dre.contains_conflict = (num_conflicts > 0)
96
88
  }
97
- CALL {
98
- WITH dre
89
+ CALL (dre) {
99
90
  OPTIONAL MATCH (dre)-[:DIFF_HAS_PROPERTY]->(dp:DiffProperty {action: "added"})
100
- WITH dre, count(dp.action) AS num_added
91
+ WITH count(dp.action) AS num_added
101
92
  SET dre.num_added = num_added
102
93
  }
103
- CALL {
104
- WITH dre
94
+ CALL (dre) {
105
95
  OPTIONAL MATCH (dre)-[:DIFF_HAS_PROPERTY]->(dp:DiffProperty {action: "updated"})
106
- WITH dre, count(dp.action) AS num_updated
96
+ WITH count(dp.action) AS num_updated
107
97
  SET dre.num_updated = num_updated
108
98
  }
109
- CALL {
110
- WITH dre
99
+ CALL (dre) {
111
100
  OPTIONAL MATCH (dre)-[:DIFF_HAS_PROPERTY]->(dp:DiffProperty {action: "removed"})
112
- WITH dre, count(dp.action) AS num_removed
101
+ WITH count(dp.action) AS num_removed
113
102
  SET dre.num_removed = num_removed
114
103
  }
115
104
  }
@@ -121,22 +110,19 @@ CALL {
121
110
  SET dr.num_conflicts = num_conflicts
122
111
  SET dr.contains_conflict = (num_conflicts > 0)
123
112
  WITH dr
124
- CALL {
125
- WITH dr
113
+ CALL (dr) {
126
114
  OPTIONAL MATCH (dr)-[:DIFF_HAS_ELEMENT]->(dre:DiffRelationshipElement {action: "added"})
127
- WITH dr, count(dre.action) AS num_added
115
+ WITH count(dre.action) AS num_added
128
116
  SET dr.num_added = num_added
129
117
  }
130
- CALL {
131
- WITH dr
118
+ CALL (dr) {
132
119
  OPTIONAL MATCH (dr)-[:DIFF_HAS_ELEMENT]->(dre:DiffRelationshipElement {action: "updated"})
133
- WITH dr, count(dre.action) AS num_updated
120
+ WITH count(dre.action) AS num_updated
134
121
  SET dr.num_updated = num_updated
135
122
  }
136
- CALL {
137
- WITH dr
123
+ CALL (dr) {
138
124
  OPTIONAL MATCH (dr)-[:DIFF_HAS_ELEMENT]->(dre:DiffRelationshipElement {action: "removed"})
139
- WITH dr, count(dre.action) AS num_removed
125
+ WITH count(dre.action) AS num_removed
140
126
  SET dr.num_removed = num_removed
141
127
  }
142
128
  }
@@ -189,19 +175,16 @@ WHERE $node_uuids IS NULL OR dn.uuid IN $node_uuids
189
175
  // handle node count updates
190
176
  // ----------------------
191
177
  WITH root, dn, coalesce(dn.num_conflicts, 0) AS previous_num_conflicts
192
- CALL {
178
+ CALL (dn) {
193
179
  // ----------------------
194
180
  // handle node num_conflicts update
195
181
  // ----------------------
196
- WITH dn
197
182
  OPTIONAL MATCH (dn)-[:DIFF_HAS_ATTRIBUTE]->(da:DiffAttribute {contains_conflict: TRUE})
198
183
  RETURN sum(da.num_conflicts) AS num_conflicts
199
184
  UNION ALL
200
- WITH dn
201
185
  OPTIONAL MATCH (dn)-[:DIFF_HAS_RELATIONSHIP]->(dr:DiffRelationship {contains_conflict: TRUE})
202
186
  RETURN sum(dr.num_conflicts) AS num_conflicts
203
187
  UNION ALL
204
- WITH dn
205
188
  OPTIONAL MATCH (dn)-[:DIFF_HAS_CONFLICT]->(dc:DiffConflict)
206
189
  RETURN count(dc) AS num_conflicts
207
190
  }
@@ -209,17 +192,16 @@ WITH root, dn, previous_num_conflicts, sum(num_conflicts) AS updated_num_conflic
209
192
  SET dn.num_conflicts = updated_num_conflicts
210
193
  SET dn.contains_conflict = (updated_num_conflicts > 0)
211
194
  WITH root, dn, updated_num_conflicts - previous_num_conflicts AS num_conflicts_delta
212
- CALL {
195
+ CALL (dn) {
213
196
  // ----------------------
214
197
  // handle node added/updated/removed updates
215
198
  // ----------------------
216
- WITH dn
217
199
  OPTIONAL MATCH (dn)-[:DIFF_HAS_ATTRIBUTE]->(da:DiffAttribute)
218
- WITH dn, collect(da.action) AS attr_actions
200
+ WITH collect(da.action) AS attr_actions
219
201
  OPTIONAL MATCH (dn)-[:DIFF_HAS_RELATIONSHIP]->(dr:DiffRelationship)
220
- WITH dn, attr_actions, collect(dr.action) AS rel_actions
221
- WITH dn, attr_actions + rel_actions AS actions
222
- WITH dn, reduce(counts = [0,0,0], a IN actions |
202
+ WITH attr_actions, collect(dr.action) AS rel_actions
203
+ WITH attr_actions + rel_actions AS actions
204
+ WITH reduce(counts = [0,0,0], a IN actions |
223
205
  CASE
224
206
  WHEN a = "added" THEN [counts[0] + 1, counts[1], counts[2]]
225
207
  WHEN a = "updated" THEN [counts[0], counts[1] + 1, counts[2]]
@@ -227,7 +209,7 @@ CALL {
227
209
  ELSE counts
228
210
  END
229
211
  ) AS action_counts
230
- WITH dn, action_counts[0] AS num_added, action_counts[1] AS num_updated, action_counts[2] AS num_removed
212
+ WITH action_counts[0] AS num_added, action_counts[1] AS num_updated, action_counts[2] AS num_removed
231
213
  SET dn.num_added = num_added
232
214
  SET dn.num_updated = num_updated
233
215
  SET dn.num_removed = num_removed
@@ -236,8 +218,7 @@ CALL {
236
218
  // handle conflict updates for parent nodes
237
219
  // ----------------------
238
220
  WITH root, dn, num_conflicts_delta
239
- CALL {
240
- WITH dn, num_conflicts_delta
221
+ CALL (dn, num_conflicts_delta) {
241
222
  OPTIONAL MATCH (dn)-[:DIFF_HAS_RELATIONSHIP|DIFF_HAS_NODE*1..]->(parent_node:DiffNode)
242
223
  SET parent_node.num_conflicts = parent_node.num_conflicts + num_conflicts_delta
243
224
  SET parent_node.contains_conflict = (parent_node.num_conflicts > 0)
@@ -246,8 +227,7 @@ CALL {
246
227
  // handle root count updates
247
228
  // ----------------------
248
229
  WITH root, sum(num_conflicts_delta) AS total_conflicts_delta
249
- CALL {
250
- WITH root, total_conflicts_delta
230
+ CALL (root, total_conflicts_delta) {
251
231
  SET root.num_conflicts = coalesce(root.num_conflicts, 0) + total_conflicts_delta
252
232
  SET root.contains_conflict = root.num_conflicts > 0
253
233
  WITH root
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections import defaultdict
4
3
  from dataclasses import dataclass, field
5
4
  from typing import TYPE_CHECKING, Any
6
5
  from uuid import uuid4
@@ -15,6 +14,7 @@ from infrahub.core.constants import (
15
14
  from infrahub.core.constants.database import DatabaseEdgeType
16
15
  from infrahub.core.timestamp import Timestamp
17
16
 
17
+ from .model.field_specifiers_map import NodeFieldSpecifierMap
18
18
  from .model.path import (
19
19
  DatabasePath,
20
20
  DiffAttribute,
@@ -23,6 +23,7 @@ from .model.path import (
23
23
  DiffRelationship,
24
24
  DiffRoot,
25
25
  DiffSingleRelationship,
26
+ NodeIdentifier,
26
27
  )
27
28
 
28
29
  if TYPE_CHECKING:
@@ -394,8 +395,7 @@ class DiffRelationshipIntermediate:
394
395
  @dataclass
395
396
  class DiffNodeIntermediate(TrackedStatusUpdates):
396
397
  force_action: DiffAction | None
397
- uuid: str
398
- kind: str
398
+ identifier: NodeIdentifier
399
399
  db_id: str
400
400
  from_time: Timestamp
401
401
  status: RelationshipStatus
@@ -403,6 +403,14 @@ class DiffNodeIntermediate(TrackedStatusUpdates):
403
403
  # {(name, identifier): DiffRelationshipIntermediate}
404
404
  relationships_by_identifier: dict[tuple[str, str], DiffRelationshipIntermediate] = field(default_factory=dict)
405
405
 
406
+ @property
407
+ def uuid(self) -> str:
408
+ return self.identifier.uuid
409
+
410
+ @property
411
+ def kind(self) -> str:
412
+ return self.identifier.kind
413
+
406
414
  def to_diff_node(self, from_time: Timestamp, include_unchanged: bool) -> DiffNode:
407
415
  attributes = []
408
416
  for attr in self.attributes_by_name.values():
@@ -424,8 +432,7 @@ class DiffNodeIntermediate(TrackedStatusUpdates):
424
432
  if self.force_action:
425
433
  action = self.force_action
426
434
  return DiffNode(
427
- uuid=self.uuid,
428
- kind=self.kind,
435
+ identifier=self.identifier,
429
436
  changed_at=changed_at,
430
437
  action=action,
431
438
  attributes=attributes,
@@ -441,11 +448,11 @@ class DiffNodeIntermediate(TrackedStatusUpdates):
441
448
  class DiffRootIntermediate:
442
449
  uuid: str
443
450
  branch: str
444
- nodes_by_id: dict[str, DiffNodeIntermediate] = field(default_factory=dict)
451
+ nodes_by_identifier: dict[NodeIdentifier, DiffNodeIntermediate] = field(default_factory=dict)
445
452
 
446
453
  def to_diff_root(self, from_time: Timestamp, to_time: Timestamp, include_unchanged: bool) -> DiffRoot:
447
454
  nodes = []
448
- for node in self.nodes_by_id.values():
455
+ for node in self.nodes_by_identifier.values():
449
456
  if node.is_empty:
450
457
  continue
451
458
  diff_node = node.to_diff_node(from_time=from_time, include_unchanged=include_unchanged)
@@ -462,7 +469,7 @@ class DiffQueryParser:
462
469
  schema_manager: SchemaManager,
463
470
  from_time: Timestamp,
464
471
  to_time: Timestamp | None = None,
465
- previous_node_field_specifiers: dict[str, set[str]] | None = None,
472
+ previous_node_field_specifiers: NodeFieldSpecifierMap | None = None,
466
473
  ) -> None:
467
474
  self.base_branch_name = base_branch.name
468
475
  self.diff_branch_name = diff_branch.name
@@ -476,9 +483,9 @@ class DiffQueryParser:
476
483
  self.diff_branched_from_time = Timestamp(diff_branch.get_branched_from())
477
484
  self._diff_root_by_branch: dict[str, DiffRootIntermediate] = {}
478
485
  self._final_diff_root_by_branch: dict[str, DiffRoot] = {}
479
- self._previous_node_field_specifiers = previous_node_field_specifiers or {}
480
- self._new_node_field_specifiers: dict[str, set[str]] | None = None
481
- self._current_node_field_specifiers: dict[str, set[str]] | None = None
486
+ self._previous_node_field_specifiers = previous_node_field_specifiers or NodeFieldSpecifierMap()
487
+ self._new_node_field_specifiers: NodeFieldSpecifierMap | None = None
488
+ self._current_node_field_specifiers: NodeFieldSpecifierMap | None = None
482
489
 
483
490
  def get_branches(self) -> set[str]:
484
491
  return set(self._final_diff_root_by_branch.keys())
@@ -490,51 +497,40 @@ class DiffQueryParser:
490
497
  return self._final_diff_root_by_branch[branch]
491
498
  return DiffRoot(from_time=self.from_time, to_time=self.to_time, uuid=str(uuid4()), branch=branch, nodes=[])
492
499
 
493
- def get_diff_node_field_specifiers(self) -> dict[str, set[str]]:
500
+ def get_diff_node_field_specifiers(self) -> NodeFieldSpecifierMap:
501
+ node_field_specifiers_map = NodeFieldSpecifierMap()
494
502
  if self.diff_branch_name not in self._diff_root_by_branch:
495
- return {}
496
- node_field_specifiers_map: dict[str, set[str]] = defaultdict(set)
503
+ return node_field_specifiers_map
497
504
  diff_root = self._diff_root_by_branch[self.diff_branch_name]
498
- for node in diff_root.nodes_by_id.values():
505
+ for node in diff_root.nodes_by_identifier.values():
499
506
  for attribute_name in node.attributes_by_name:
500
- node_field_specifiers_map[node.uuid].add(attribute_name)
507
+ node_field_specifiers_map.add_entry(node_uuid=node.uuid, kind=node.kind, field_name=attribute_name)
501
508
  for relationship_diff in node.relationships_by_identifier.values():
502
- node_field_specifiers_map[node.uuid].add(relationship_diff.identifier)
509
+ node_field_specifiers_map.add_entry(
510
+ node_uuid=node.uuid, kind=node.kind, field_name=relationship_diff.identifier
511
+ )
503
512
  return node_field_specifiers_map
504
513
 
505
- def _remove_node_specifiers(
506
- self, node_specifiers: dict[str, set[str]], node_specifiers_to_remove: dict[str, set[str]]
507
- ) -> dict[str, set[str]]:
508
- final_node_specifiers: dict[str, set[str]] = defaultdict(set)
509
- for node_uuid, field_names_set in node_specifiers.items():
510
- specifiers_to_remove = node_specifiers_to_remove.get(node_uuid, set())
511
- final_specifiers = field_names_set - specifiers_to_remove
512
- if final_specifiers:
513
- final_node_specifiers[node_uuid] = final_specifiers
514
- return final_node_specifiers
515
-
516
- def get_new_node_field_specifiers(self) -> dict[str, set[str]]:
514
+ def get_new_node_field_specifiers(self) -> NodeFieldSpecifierMap:
517
515
  if self._new_node_field_specifiers is not None:
518
516
  return self._new_node_field_specifiers
519
517
  branch_node_specifiers = self.get_diff_node_field_specifiers()
520
- new_node_field_specifiers = self._remove_node_specifiers(
521
- branch_node_specifiers, self._previous_node_field_specifiers
522
- )
523
- self._new_node_field_specifiers = new_node_field_specifiers
524
- return new_node_field_specifiers
518
+ self._new_node_field_specifiers = branch_node_specifiers - self._previous_node_field_specifiers
519
+ return self._new_node_field_specifiers
525
520
 
526
- def get_current_node_field_specifiers(self) -> dict[str, set[str]]:
521
+ def get_current_node_field_specifiers(self) -> NodeFieldSpecifierMap:
527
522
  if self._current_node_field_specifiers is not None:
528
523
  return self._current_node_field_specifiers
529
524
  new_node_field_specifiers = self.get_new_node_field_specifiers()
530
- current_node_field_specifiers = self._remove_node_specifiers(
531
- self._previous_node_field_specifiers, new_node_field_specifiers
532
- )
533
- self._current_node_field_specifiers = current_node_field_specifiers
534
- return current_node_field_specifiers
525
+ self._current_node_field_specifiers = self._previous_node_field_specifiers - new_node_field_specifiers
526
+ return self._current_node_field_specifiers
535
527
 
536
528
  def read_result(self, query_result: QueryResult) -> None:
537
- path = query_result.get_path(label="diff_path")
529
+ try:
530
+ path = query_result.get_path(label="diff_path")
531
+ except ValueError:
532
+ # the path was null, so nothing to read
533
+ return
538
534
  database_path = DatabasePath.from_cypher_path(cypher_path=path)
539
535
  self._parse_path(database_path=database_path)
540
536
  self._current_node_field_specifiers = None
@@ -565,11 +561,12 @@ class DiffQueryParser:
565
561
  return self._diff_root_by_branch[branch]
566
562
 
567
563
  def _get_diff_node(self, database_path: DatabasePath, diff_root: DiffRootIntermediate) -> DiffNodeIntermediate:
568
- node_id = database_path.node_id
569
- if node_id not in diff_root.nodes_by_id:
570
- diff_root.nodes_by_id[node_id] = DiffNodeIntermediate(
571
- uuid=node_id,
572
- kind=database_path.node_kind,
564
+ identifier = NodeIdentifier(
565
+ uuid=database_path.node_id, kind=database_path.node_kind, db_id=database_path.node_db_id
566
+ )
567
+ if identifier not in diff_root.nodes_by_identifier:
568
+ diff_root.nodes_by_identifier[identifier] = DiffNodeIntermediate(
569
+ identifier=identifier,
573
570
  db_id=database_path.node_db_id,
574
571
  from_time=database_path.node_changed_at,
575
572
  status=database_path.node_status,
@@ -577,20 +574,7 @@ class DiffQueryParser:
577
574
  if database_path.node_branch_support is BranchSupportType.AGNOSTIC
578
575
  else None,
579
576
  )
580
- diff_node = diff_root.nodes_by_id[node_id]
581
- # special handling for nodes that have their kind updated, which results in 2 nodes with the same uuid
582
- if diff_node.db_id != database_path.node_db_id and (
583
- database_path.node_changed_at > diff_node.from_time
584
- or (
585
- database_path.node_changed_at >= diff_node.from_time
586
- and (diff_node.status, database_path.node_status)
587
- == (RelationshipStatus.DELETED, RelationshipStatus.ACTIVE)
588
- )
589
- ):
590
- diff_node.kind = database_path.node_kind
591
- diff_node.db_id = database_path.node_db_id
592
- diff_node.from_time = database_path.node_changed_at
593
- diff_node.status = database_path.node_status
577
+ diff_node = diff_root.nodes_by_identifier[identifier]
594
578
  diff_node.track_database_path(database_path=database_path)
595
579
  return diff_node
596
580
 
@@ -634,7 +618,9 @@ class DiffQueryParser:
634
618
  from_time = self.from_time
635
619
  if branch_name == self.base_branch_name:
636
620
  new_node_field_specifiers = self.get_new_node_field_specifiers()
637
- if attribute_name in new_node_field_specifiers.get(diff_node.uuid, set()):
621
+ if new_node_field_specifiers.has_entry(
622
+ node_uuid=diff_node.uuid, kind=diff_node.kind, field_name=attribute_name
623
+ ):
638
624
  from_time = self.diff_branched_from_time
639
625
  if attribute_name not in diff_node.attributes_by_name:
640
626
  diff_node.attributes_by_name[attribute_name] = DiffAttributeIntermediate(
@@ -678,7 +664,9 @@ class DiffQueryParser:
678
664
  from_time = self.from_time
679
665
  if branch_name == self.base_branch_name:
680
666
  new_node_field_specifiers = self.get_new_node_field_specifiers()
681
- if relationship_schema.get_identifier() in new_node_field_specifiers.get(diff_node.uuid, set()):
667
+ if new_node_field_specifiers.has_entry(
668
+ node_uuid=diff_node.uuid, kind=diff_node.kind, field_name=relationship_schema.get_identifier()
669
+ ):
682
670
  from_time = self.diff_branched_from_time
683
671
  diff_relationship = DiffRelationshipIntermediate(
684
672
  name=relationship_schema.name,
@@ -700,8 +688,10 @@ class DiffQueryParser:
700
688
  branch_diff_root = self._diff_root_by_branch.get(branch)
701
689
  if not branch_diff_root:
702
690
  continue
703
- for node_id, diff_node in branch_diff_root.nodes_by_id.items():
704
- base_diff_node = base_diff_root.nodes_by_id.get(node_id)
691
+ base_diff_nodes_by_uuid = {n.uuid: n for n in base_diff_root.nodes_by_identifier.values()}
692
+ for identifier, diff_node in branch_diff_root.nodes_by_identifier.items():
693
+ # changes on a base branch node with a given UUID should apply to all diff branch nodes with that UUID
694
+ base_diff_node = base_diff_nodes_by_uuid.get(identifier.uuid)
705
695
  if not base_diff_node:
706
696
  continue
707
697
  self._apply_attribute_previous_values(diff_node=diff_node, base_diff_node=base_diff_node)
@@ -771,7 +761,7 @@ class DiffQueryParser:
771
761
  base_diff_root = self._diff_root_by_branch.get(self.base_branch_name)
772
762
  if not base_diff_root:
773
763
  return
774
- for node_diff in base_diff_root.nodes_by_id.values():
764
+ for node_diff in base_diff_root.nodes_by_identifier.values():
775
765
  for attribute_diff in node_diff.attributes_by_name.values():
776
766
  for property_diff in attribute_diff.properties_by_type.values():
777
767
  ordered_diff_values = property_diff.get_ordered_values_asc()