infrahub-server 1.6.3__py3-none-any.whl → 1.7.0b0__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 (161) hide show
  1. infrahub/actions/tasks.py +4 -2
  2. infrahub/api/schema.py +3 -1
  3. infrahub/artifacts/tasks.py +1 -0
  4. infrahub/auth.py +2 -2
  5. infrahub/cli/db.py +6 -6
  6. infrahub/computed_attribute/gather.py +3 -4
  7. infrahub/computed_attribute/tasks.py +23 -6
  8. infrahub/config.py +8 -0
  9. infrahub/constants/enums.py +12 -0
  10. infrahub/core/account.py +5 -8
  11. infrahub/core/attribute.py +106 -108
  12. infrahub/core/branch/models.py +44 -71
  13. infrahub/core/branch/tasks.py +5 -3
  14. infrahub/core/changelog/diff.py +1 -20
  15. infrahub/core/changelog/models.py +0 -7
  16. infrahub/core/constants/__init__.py +17 -0
  17. infrahub/core/constants/database.py +0 -1
  18. infrahub/core/constants/schema.py +0 -1
  19. infrahub/core/convert_object_type/repository_conversion.py +3 -4
  20. infrahub/core/diff/data_check_synchronizer.py +3 -2
  21. infrahub/core/diff/enricher/cardinality_one.py +1 -1
  22. infrahub/core/diff/merger/merger.py +27 -1
  23. infrahub/core/diff/merger/serializer.py +3 -10
  24. infrahub/core/diff/model/diff.py +1 -1
  25. infrahub/core/diff/query/merge.py +376 -135
  26. infrahub/core/graph/__init__.py +1 -1
  27. infrahub/core/graph/constraints.py +2 -2
  28. infrahub/core/graph/schema.py +2 -12
  29. infrahub/core/manager.py +132 -126
  30. infrahub/core/metadata/__init__.py +0 -0
  31. infrahub/core/metadata/interface.py +37 -0
  32. infrahub/core/metadata/model.py +31 -0
  33. infrahub/core/metadata/query/__init__.py +0 -0
  34. infrahub/core/metadata/query/node_metadata.py +301 -0
  35. infrahub/core/migrations/graph/__init__.py +4 -0
  36. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +3 -8
  37. infrahub/core/migrations/graph/m017_add_core_profile.py +5 -2
  38. infrahub/core/migrations/graph/m018_uniqueness_nulls.py +2 -1
  39. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +0 -10
  40. infrahub/core/migrations/graph/m020_duplicate_edges.py +0 -8
  41. infrahub/core/migrations/graph/m025_uniqueness_nulls.py +2 -1
  42. infrahub/core/migrations/graph/m026_0000_prefix_fix.py +2 -1
  43. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +0 -1
  44. infrahub/core/migrations/graph/m031_check_number_attributes.py +2 -2
  45. infrahub/core/migrations/graph/m038_redo_0000_prefix_fix.py +2 -1
  46. infrahub/core/migrations/graph/m049_remove_is_visible_relationship.py +38 -0
  47. infrahub/core/migrations/graph/m050_backfill_vertex_metadata.py +168 -0
  48. infrahub/core/migrations/query/attribute_add.py +17 -6
  49. infrahub/core/migrations/query/attribute_remove.py +19 -5
  50. infrahub/core/migrations/query/attribute_rename.py +21 -5
  51. infrahub/core/migrations/query/node_duplicate.py +19 -4
  52. infrahub/core/migrations/schema/attribute_kind_update.py +25 -7
  53. infrahub/core/migrations/schema/attribute_supports_profile.py +3 -1
  54. infrahub/core/migrations/schema/models.py +3 -0
  55. infrahub/core/migrations/schema/node_attribute_add.py +4 -1
  56. infrahub/core/migrations/schema/node_remove.py +24 -2
  57. infrahub/core/migrations/schema/tasks.py +4 -1
  58. infrahub/core/migrations/shared.py +13 -6
  59. infrahub/core/models.py +6 -6
  60. infrahub/core/node/__init__.py +156 -57
  61. infrahub/core/node/create.py +7 -3
  62. infrahub/core/node/standard.py +100 -14
  63. infrahub/core/property.py +0 -1
  64. infrahub/core/protocols_base.py +6 -2
  65. infrahub/core/query/__init__.py +6 -7
  66. infrahub/core/query/attribute.py +161 -46
  67. infrahub/core/query/branch.py +57 -69
  68. infrahub/core/query/diff.py +4 -4
  69. infrahub/core/query/node.py +618 -180
  70. infrahub/core/query/relationship.py +449 -300
  71. infrahub/core/query/standard_node.py +25 -5
  72. infrahub/core/query/utils.py +2 -4
  73. infrahub/core/relationship/constraints/profiles_removal.py +168 -0
  74. infrahub/core/relationship/model.py +293 -139
  75. infrahub/core/schema/attribute_parameters.py +1 -28
  76. infrahub/core/schema/attribute_schema.py +17 -11
  77. infrahub/core/schema/manager.py +63 -43
  78. infrahub/core/schema/relationship_schema.py +6 -2
  79. infrahub/core/schema/schema_branch.py +48 -76
  80. infrahub/core/task/task.py +4 -2
  81. infrahub/core/utils.py +0 -22
  82. infrahub/core/validators/attribute/kind.py +2 -5
  83. infrahub/core/validators/determiner.py +3 -3
  84. infrahub/database/__init__.py +3 -3
  85. infrahub/dependencies/builder/constraint/grouped/node_runner.py +2 -0
  86. infrahub/dependencies/builder/constraint/relationship_manager/profiles_removal.py +8 -0
  87. infrahub/dependencies/registry.py +2 -0
  88. infrahub/display_labels/tasks.py +12 -3
  89. infrahub/git/integrator.py +18 -18
  90. infrahub/git/tasks.py +1 -1
  91. infrahub/graphql/app.py +2 -2
  92. infrahub/graphql/constants.py +3 -0
  93. infrahub/graphql/context.py +1 -1
  94. infrahub/graphql/initialization.py +11 -0
  95. infrahub/graphql/loaders/account.py +134 -0
  96. infrahub/graphql/loaders/node.py +5 -12
  97. infrahub/graphql/loaders/peers.py +5 -7
  98. infrahub/graphql/manager.py +158 -18
  99. infrahub/graphql/metadata.py +91 -0
  100. infrahub/graphql/models.py +33 -3
  101. infrahub/graphql/mutations/account.py +5 -5
  102. infrahub/graphql/mutations/attribute.py +0 -2
  103. infrahub/graphql/mutations/branch.py +9 -5
  104. infrahub/graphql/mutations/computed_attribute.py +1 -1
  105. infrahub/graphql/mutations/display_label.py +1 -1
  106. infrahub/graphql/mutations/hfid.py +1 -1
  107. infrahub/graphql/mutations/ipam.py +4 -6
  108. infrahub/graphql/mutations/main.py +9 -4
  109. infrahub/graphql/mutations/profile.py +16 -22
  110. infrahub/graphql/mutations/proposed_change.py +4 -4
  111. infrahub/graphql/mutations/relationship.py +40 -10
  112. infrahub/graphql/mutations/repository.py +14 -12
  113. infrahub/graphql/mutations/schema.py +2 -2
  114. infrahub/graphql/queries/branch.py +62 -6
  115. infrahub/graphql/queries/diff/tree.py +5 -5
  116. infrahub/graphql/resolvers/account_metadata.py +84 -0
  117. infrahub/graphql/resolvers/ipam.py +6 -8
  118. infrahub/graphql/resolvers/many_relationship.py +77 -35
  119. infrahub/graphql/resolvers/resolver.py +16 -12
  120. infrahub/graphql/resolvers/single_relationship.py +87 -23
  121. infrahub/graphql/subscription/graphql_query.py +2 -0
  122. infrahub/graphql/types/__init__.py +0 -1
  123. infrahub/graphql/types/attribute.py +10 -5
  124. infrahub/graphql/types/branch.py +40 -53
  125. infrahub/graphql/types/enums.py +3 -0
  126. infrahub/graphql/types/metadata.py +28 -0
  127. infrahub/graphql/types/node.py +22 -2
  128. infrahub/graphql/types/relationship.py +10 -2
  129. infrahub/graphql/types/standard_node.py +4 -3
  130. infrahub/hfid/tasks.py +12 -3
  131. infrahub/profiles/gather.py +56 -0
  132. infrahub/profiles/mandatory_fields_checker.py +116 -0
  133. infrahub/profiles/models.py +66 -0
  134. infrahub/profiles/node_applier.py +153 -12
  135. infrahub/profiles/queries/get_profile_data.py +143 -31
  136. infrahub/profiles/tasks.py +79 -27
  137. infrahub/profiles/triggers.py +22 -0
  138. infrahub/proposed_change/tasks.py +4 -1
  139. infrahub/tasks/artifact.py +1 -0
  140. infrahub/transformations/tasks.py +2 -2
  141. infrahub/trigger/catalogue.py +2 -0
  142. infrahub/trigger/models.py +1 -0
  143. infrahub/trigger/setup.py +3 -3
  144. infrahub/trigger/tasks.py +3 -0
  145. infrahub/validators/tasks.py +1 -0
  146. infrahub/webhook/models.py +1 -1
  147. infrahub/webhook/tasks.py +1 -1
  148. infrahub/workers/dependencies.py +9 -3
  149. infrahub/workers/infrahub_async.py +13 -4
  150. infrahub/workflows/catalogue.py +19 -0
  151. infrahub_sdk/node/constants.py +1 -0
  152. infrahub_sdk/node/related_node.py +13 -4
  153. infrahub_sdk/node/relationship.py +8 -0
  154. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/METADATA +17 -16
  155. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/RECORD +161 -143
  156. infrahub_testcontainers/container.py +3 -3
  157. infrahub_testcontainers/docker-compose-cluster.test.yml +7 -7
  158. infrahub_testcontainers/docker-compose.test.yml +13 -5
  159. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/WHEEL +0 -0
  160. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/entry_points.txt +0 -0
  161. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
+ from infrahub.core.constants import GLOBAL_BRANCH_NAME
5
6
  from infrahub.core.query import Query, QueryType
6
7
 
7
8
  if TYPE_CHECKING:
@@ -14,6 +15,7 @@ class DiffMergeQuery(Query):
14
15
  name = "diff_merge"
15
16
  type = QueryType.WRITE
16
17
  insert_return = False
18
+ raise_error_if_empty = False
17
19
 
18
20
  def __init__(
19
21
  self,
@@ -56,7 +58,7 @@ END AS node_db_id
56
58
  // ------------------------------
57
59
  CALL (node_diff_map, node_db_id) {
58
60
  MATCH (n:Node {uuid: node_diff_map.uuid})-[n_is_part_of:IS_PART_OF]->(:Root)
59
- WHERE node_db_id IS NULL OR %(id_func)s(n) = node_db_id
61
+ WHERE node_db_id IS NULL OR elementId(n) = node_db_id
60
62
  AND n_is_part_of.branch IN [$source_branch, $target_branch]
61
63
  RETURN n
62
64
  ORDER BY n_is_part_of.branch_level DESC, n_is_part_of.from DESC, n_is_part_of.status ASC
@@ -79,65 +81,80 @@ CALL (n, node_diff_map, is_node_kind_migration) {
79
81
  AND is_node_kind_migration = FALSE
80
82
  MATCH (root:Root)
81
83
  // ------------------------------
84
+ // get from_user_id from source branch edge for creating new edges
85
+ // and to_user_id from source branch deleted edge for closing edges
86
+ // ------------------------------
87
+ CALL (n, root, node_rel_status) {
88
+ OPTIONAL MATCH (n)-[source_is_part_of:IS_PART_OF {branch: $source_branch, status: node_rel_status}]->(root)
89
+ WHERE source_is_part_of.from <= $at AND source_is_part_of.to IS NULL
90
+ RETURN source_is_part_of.from_user_id AS source_from_user_id
91
+ ORDER BY source_is_part_of.from DESC
92
+ LIMIT 1
93
+ }
94
+ // ------------------------------
82
95
  // set IS_PART_OF.to, optionally, target branch
83
96
  // ------------------------------
84
- WITH root, n, node_rel_status
85
- CALL (root, n, node_rel_status) {
97
+ WITH root, n, node_rel_status, source_from_user_id
98
+ CALL (root, n, node_rel_status, source_from_user_id) {
86
99
  OPTIONAL MATCH (root)<-[target_r_root:IS_PART_OF {branch: $target_branch, status: "active"}]-(n)
87
100
  WHERE node_rel_status = "deleted"
88
101
  AND target_r_root.from <= $at AND target_r_root.to IS NULL
89
- SET target_r_root.to = $at
102
+ SET target_r_root.to = $at, target_r_root.to_user_id = source_from_user_id
90
103
  }
91
104
  // ------------------------------
92
105
  // create new IS_PART_OF relationship on target_branch
106
+ // also set created_at/created_by on Node vertex when adding
93
107
  // ------------------------------
94
- WITH root, n, node_rel_status
95
- CALL (root, n, node_rel_status) {
108
+ WITH root, n, node_rel_status, source_from_user_id
109
+ CALL (root, n, node_rel_status, source_from_user_id) {
96
110
  OPTIONAL MATCH (root)<-[r_root:IS_PART_OF {branch: $target_branch}]-(n)
97
111
  WHERE r_root.status = node_rel_status
98
112
  AND r_root.from <= $at
99
113
  AND (r_root.to >= $at OR r_root.to IS NULL)
100
- WITH r_root
114
+ WITH n, r_root, source_from_user_id
101
115
  WHERE r_root IS NULL
102
116
  CREATE (root)
103
- <-[:IS_PART_OF { branch: $target_branch, branch_level: $branch_level, from: $at, status: node_rel_status }]
117
+ <-[:IS_PART_OF { branch: $target_branch, branch_level: $branch_level, from: $at, status: node_rel_status, from_user_id: source_from_user_id }]
104
118
  -(n)
119
+ WITH node_rel_status
120
+ WHERE node_rel_status = "active"
121
+ SET n.created_at = $at, n.created_by = source_from_user_id
105
122
  }
106
123
  // ------------------------------
107
124
  // shortcut to delete all attributes and relationships for this node if the node is deleted
108
125
  // ------------------------------
109
- CALL (n, node_rel_status) {
110
- WITH n, node_rel_status
126
+ CALL (n, node_rel_status, source_from_user_id) {
127
+ WITH n, node_rel_status, source_from_user_id
111
128
  WHERE node_rel_status = "deleted"
112
129
  CALL (n) {
113
- OPTIONAL MATCH (n)-[rel1:IS_RELATED]-(:Relationship)-[rel2]-(p)
130
+ OPTIONAL MATCH (n)-[rel1:IS_RELATED]-(attr_rel:Relationship)-[rel2]-(p)
114
131
  WHERE (p.uuid IS NULL OR n.uuid <> p.uuid)
115
132
  AND rel1.branch = $target_branch
116
133
  AND rel2.branch = $target_branch
117
134
  AND rel1.status = "active"
118
135
  AND rel2.status = "active"
119
- RETURN rel1, rel2
136
+ RETURN rel1, rel2, attr_rel
120
137
  UNION
121
- OPTIONAL MATCH (n)-[rel1:HAS_ATTRIBUTE]->(:Attribute)-[rel2]->()
138
+ OPTIONAL MATCH (n)-[rel1:HAS_ATTRIBUTE]->(attr_rel:Attribute)-[rel2]->()
122
139
  WHERE type(rel2) <> "HAS_ATTRIBUTE"
123
140
  AND rel1.branch = $target_branch
124
141
  AND rel2.branch = $target_branch
125
142
  AND rel1.status = "active"
126
143
  AND rel2.status = "active"
127
- RETURN rel1, rel2
144
+ RETURN rel1, rel2, attr_rel
128
145
  }
129
- WITH rel1, rel2
146
+ WITH rel1, rel2, attr_rel, source_from_user_id
130
147
  WHERE rel1.to IS NULL
131
148
  AND rel2.to IS NULL
132
149
  AND rel1.from <= $at
133
150
  AND rel2.from <= $at
134
- SET rel1.to = $at
135
- SET rel2.to = $at
151
+ SET rel1.to = $at, rel1.to_user_id = source_from_user_id
152
+ SET rel2.to = $at, rel2.to_user_id = source_from_user_id
136
153
  // ------------------------------
137
154
  // and delete HAS_OWNER and HAS_SOURCE edges to this node if the node is deleted
138
155
  // ------------------------------
139
- WITH n
140
- CALL (n) {
156
+ WITH n, source_from_user_id
157
+ CALL (n, source_from_user_id) {
141
158
  CALL (n) {
142
159
  MATCH (n)<-[rel:HAS_OWNER]-()
143
160
  WHERE rel.branch = $target_branch
@@ -153,7 +170,7 @@ CALL (n, node_diff_map, is_node_kind_migration) {
153
170
  AND rel.to IS NULL
154
171
  RETURN rel
155
172
  }
156
- SET rel.to = $at
173
+ SET rel.to = $at, rel.to_user_id = source_from_user_id
157
174
  }
158
175
  }
159
176
  }
@@ -180,32 +197,48 @@ CALL (n, node_diff_map, is_node_kind_migration) {
180
197
  ORDER BY has_attr.from DESC
181
198
  LIMIT 1
182
199
  }
183
- WITH n, attr_rel_status, a
200
+ // ------------------------------
201
+ // get from_user_id from source branch edge
202
+ // ------------------------------
203
+ CALL (n, a, attr_rel_status) {
204
+ OPTIONAL MATCH (n)-[source_has_attr:HAS_ATTRIBUTE {branch: $source_branch, status: attr_rel_status}]->(a)
205
+ WHERE source_has_attr.from <= $at AND source_has_attr.to IS NULL
206
+ RETURN source_has_attr.from_user_id AS attr_source_from_user_id
207
+ ORDER BY source_has_attr.from DESC
208
+ LIMIT 1
209
+ }
210
+ WITH n, attr_rel_status, a, attr_source_from_user_id
184
211
  // ------------------------------
185
212
  // set HAS_ATTRIBUTE.to on target branch if necessary
213
+ // set Attribute.created_at/by vertex when adding
186
214
  // ------------------------------
187
- CALL (n, attr_rel_status, a) {
215
+ CALL (n, attr_rel_status, a, attr_source_from_user_id) {
188
216
  OPTIONAL MATCH (n)
189
217
  -[target_r_attr:HAS_ATTRIBUTE {branch: $target_branch, status: "active"}]
190
218
  ->(a)
191
219
  WHERE attr_rel_status = "deleted"
192
220
  AND target_r_attr.from <= $at AND target_r_attr.to IS NULL
193
- SET target_r_attr.to = $at
221
+ SET target_r_attr.to = $at, target_r_attr.to_user_id = attr_source_from_user_id
194
222
  }
195
- WITH n, attr_rel_status, a
223
+ WITH n, attr_rel_status, a, attr_source_from_user_id
196
224
  // ------------------------------
197
225
  // conditionally create new HAS_ATTRIBUTE relationship on target_branch, if necessary
226
+ // also set created_at/created_by on Attribute vertex when adding
198
227
  // ------------------------------
199
- CALL (n, attr_rel_status, a) {
200
- WITH n, attr_rel_status, a
228
+ CALL (n, attr_rel_status, a, attr_source_from_user_id) {
229
+ WITH n, attr_rel_status, a, attr_source_from_user_id
201
230
  WHERE a IS NOT NULL
202
231
  OPTIONAL MATCH (n)-[r_attr:HAS_ATTRIBUTE {branch: $target_branch}]->(a)
203
232
  WHERE r_attr.status = attr_rel_status
204
233
  AND r_attr.from <= $at
205
234
  AND (r_attr.to >= $at OR r_attr.to IS NULL)
206
- WITH r_attr
235
+ WITH a, r_attr, attr_source_from_user_id
207
236
  WHERE r_attr IS NULL
208
- CREATE (n)-[:HAS_ATTRIBUTE { branch: $target_branch, branch_level: $branch_level, from: $at, status: attr_rel_status }]->(a)
237
+ CREATE (n)-[:HAS_ATTRIBUTE { branch: $target_branch, branch_level: $branch_level, from: $at, status: attr_rel_status, from_user_id: attr_source_from_user_id }]->(a)
238
+ WITH attr_rel_status
239
+ WHERE attr_rel_status = "active"
240
+ AND a.created_at IS NULL
241
+ SET a.created_at = $at, a.created_by = attr_source_from_user_id
209
242
  }
210
243
  RETURN 1 AS done
211
244
  }
@@ -236,7 +269,7 @@ CALL (n, node_diff_map, is_node_kind_migration) {
236
269
  // ------------------------------
237
270
  CALL (rel_peer_id, rel_peer_db_id) {
238
271
  MATCH (rel_peer:Node {uuid: rel_peer_id})-[target_is_part_of:IS_PART_OF]->(:Root)
239
- WHERE (rel_peer_db_id IS NULL OR %(id_func)s(rel_peer) = rel_peer_db_id)
272
+ WHERE (rel_peer_db_id IS NULL OR elementId(rel_peer) = rel_peer_db_id)
240
273
  AND target_is_part_of.branch IN [$source_branch, $target_branch]
241
274
  RETURN rel_peer
242
275
  ORDER BY target_is_part_of.branch_level DESC, target_is_part_of.from DESC, target_is_part_of.status ASC
@@ -244,7 +277,7 @@ CALL (n, node_diff_map, is_node_kind_migration) {
244
277
  }
245
278
  WITH rel_name, related_rel_status, rel_peer
246
279
  // ------------------------------
247
- // determine the directions of each IS_RELATED
280
+ // determine the directions of each IS_RELATED and get from_user_id from source branch
248
281
  // ------------------------------
249
282
  CALL (n, rel_name, rel_peer, related_rel_status) {
250
283
  MATCH (n)
@@ -268,26 +301,32 @@ CALL (n, node_diff_map, is_node_kind_migration) {
268
301
  ELSE "l"
269
302
  END AS r2_dir,
270
303
  source_r_rel_1.hierarchy AS r1_hierarchy,
271
- source_r_rel_2.hierarchy AS r2_hierarchy
304
+ source_r_rel_2.hierarchy AS r2_hierarchy,
305
+ source_r_rel_1.from_user_id AS r1_from_user_id,
306
+ source_r_rel_2.from_user_id AS r2_from_user_id
272
307
  }
273
- WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, rel_name, rel_peer, related_rel_status
274
- CALL (n, rel_name, rel_peer, related_rel_status) {
308
+ WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, r1_from_user_id, r2_from_user_id, rel_name, rel_peer, related_rel_status
309
+ // ------------------------------
310
+ // set IS_RELATED.to on target branch when deleting relationship
311
+ // ------------------------------
312
+ CALL (n, r, rel_name, rel_peer, related_rel_status, r1_from_user_id, r2_from_user_id) {
275
313
  OPTIONAL MATCH (n)
276
314
  -[target_r_rel_1:IS_RELATED {branch: $target_branch, status: "active"}]
277
- -(:Relationship {name: rel_name})
315
+ -(r:Relationship {name: rel_name})
278
316
  -[target_r_rel_2:IS_RELATED {branch: $target_branch, status: "active"}]
279
317
  -(rel_peer)
280
318
  WHERE related_rel_status = "deleted"
281
319
  AND target_r_rel_1.from <= $at AND target_r_rel_1.to IS NULL
282
320
  AND target_r_rel_2.from <= $at AND target_r_rel_2.to IS NULL
283
- SET target_r_rel_1.to = $at
284
- SET target_r_rel_2.to = $at
321
+ SET target_r_rel_1.to = $at, target_r_rel_1.to_user_id = r1_from_user_id
322
+ SET target_r_rel_2.to = $at, target_r_rel_2.to_user_id = r2_from_user_id
285
323
  }
286
- WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, rel_name, rel_peer, related_rel_status
324
+ WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, r1_from_user_id, r2_from_user_id, rel_name, rel_peer, related_rel_status
287
325
  // ------------------------------
288
326
  // conditionally create new IS_RELATED relationships on target_branch, if necessary
327
+ // also set created_at/created_by on Relationship vertex when adding
289
328
  // ------------------------------
290
- CALL (n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, rel_name, rel_peer, related_rel_status) {
329
+ CALL (n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, r1_from_user_id, r2_from_user_id, rel_name, rel_peer, related_rel_status) {
291
330
  OPTIONAL MATCH (n)
292
331
  -[r_rel_1:IS_RELATED {branch: $target_branch, status: related_rel_status}]
293
332
  -(:Relationship {name: rel_name})
@@ -297,46 +336,50 @@ CALL (n, node_diff_map, is_node_kind_migration) {
297
336
  AND (r_rel_1.to >= $at OR r_rel_1.to IS NULL)
298
337
  AND r_rel_2.from <= $at
299
338
  AND (r_rel_2.to >= $at OR r_rel_2.to IS NULL)
300
- WITH rel_peer, r_rel_1, r_rel_2
339
+ WITH r, rel_peer, r_rel_1, r_rel_2, r1_from_user_id, r2_from_user_id
301
340
  WHERE r_rel_1 IS NULL
302
341
  AND r_rel_2 IS NULL
342
+ WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, related_rel_status, r1_from_user_id, r2_from_user_id
303
343
  // ------------------------------
304
344
  // create IS_RELATED relationships with directions maintained from source
305
345
  // ------------------------------
306
- CALL (n, r, r1_dir, r1_hierarchy, related_rel_status) {
307
- WITH n, r, r1_dir, r1_hierarchy, related_rel_status
346
+ CALL (n, r, r1_dir, r1_hierarchy, related_rel_status, r1_from_user_id) {
347
+ WITH n, r, r1_dir, r1_hierarchy, related_rel_status, r1_from_user_id
308
348
  WHERE r1_dir = "r"
309
349
  CREATE (n)
310
- -[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status, hierarchy: r1_hierarchy}]
350
+ -[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status, hierarchy: r1_hierarchy, from_user_id: r1_from_user_id}]
311
351
  ->(r)
312
352
  }
313
- CALL (n, r, r1_dir, r1_hierarchy, related_rel_status) {
314
- WITH n, r, r1_dir, r1_hierarchy, related_rel_status
353
+ CALL (n, r, r1_dir, r1_hierarchy, related_rel_status, r1_from_user_id) {
354
+ WITH n, r, r1_dir, r1_hierarchy, related_rel_status, r1_from_user_id
315
355
  WHERE r1_dir = "l"
316
356
  CREATE (n)
317
- <-[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status, hierarchy: r1_hierarchy}]
357
+ <-[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status, hierarchy: r1_hierarchy, from_user_id: r1_from_user_id}]
318
358
  -(r)
319
359
  }
320
- CALL (r, rel_peer, r2_dir, r2_hierarchy, related_rel_status) {
321
- WITH r, rel_peer, r2_dir, r2_hierarchy, related_rel_status
360
+ CALL (r, rel_peer, r2_dir, r2_hierarchy, related_rel_status, r2_from_user_id) {
361
+ WITH r, rel_peer, r2_dir, r2_hierarchy, related_rel_status, r2_from_user_id
322
362
  WHERE r2_dir = "r"
323
363
  CREATE (r)
324
- -[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status, hierarchy: r2_hierarchy}]
364
+ -[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status, hierarchy: r2_hierarchy, from_user_id: r2_from_user_id}]
325
365
  ->(rel_peer)
326
366
  }
327
- CALL (r, rel_peer, r2_dir, r2_hierarchy, related_rel_status) {
328
- WITH r, rel_peer, r2_dir, r2_hierarchy, related_rel_status
367
+ CALL (r, rel_peer, r2_dir, r2_hierarchy, related_rel_status, r2_from_user_id) {
368
+ WITH r, rel_peer, r2_dir, r2_hierarchy, related_rel_status, r2_from_user_id
329
369
  WHERE r2_dir = "l"
330
370
  CREATE (r)
331
- <-[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status, hierarchy: r2_hierarchy}]
371
+ <-[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status, hierarchy: r2_hierarchy, from_user_id: r2_from_user_id}]
332
372
  -(rel_peer)
333
373
  }
374
+ // set Relationship vertex metadata when adding
375
+ WITH r
376
+ WHERE r.created_at IS NULL
377
+ SET r.created_at = $at, r.created_by = r1_from_user_id
334
378
  }
335
379
  }
336
380
  }
337
381
  }
338
- RETURN 1 AS done
339
- """ % {"id_func": db.get_id_function_name()}
382
+ """
340
383
  self.add_to_query(query=query)
341
384
 
342
385
 
@@ -391,7 +434,7 @@ CALL (attr_rel_prop_diff, node_db_id, peer_db_id) {
391
434
  -[has_attr:HAS_ATTRIBUTE]
392
435
  ->(attr:Attribute {name: attr_rel_prop_diff.attribute_name})
393
436
  WHERE attr_rel_prop_diff.attribute_name IS NOT NULL
394
- AND (node_db_id IS NULL OR %(id_func)s(n) = node_db_id)
437
+ AND (node_db_id IS NULL OR elementId(n) = node_db_id)
395
438
  AND has_attr.branch IN [$source_branch, $target_branch]
396
439
  RETURN attr
397
440
  ORDER BY has_attr.from DESC
@@ -404,8 +447,8 @@ CALL (attr_rel_prop_diff, node_db_id, peer_db_id) {
404
447
  -[r2:IS_RELATED]
405
448
  -(rel_peer:Node {uuid: attr_rel_prop_diff.peer_uuid})
406
449
  WHERE attr_rel_prop_diff.relationship_id IS NOT NULL
407
- AND (node_db_id IS NULL OR %(id_func)s(n) = node_db_id)
408
- AND (peer_db_id IS NULL OR %(id_func)s(rel_peer) = peer_db_id)
450
+ AND (node_db_id IS NULL OR elementId(n) = node_db_id)
451
+ AND (peer_db_id IS NULL OR elementId(rel_peer) = peer_db_id)
409
452
  AND r1.branch IN [$source_branch, $target_branch]
410
453
  AND r2.branch IN [$source_branch, $target_branch]
411
454
  RETURN rel
@@ -425,14 +468,14 @@ CALL (attr_rel_prop_diff, node_db_id, peer_db_id) {
425
468
  CALL (attr_rel, property_diff, peer_db_id) {
426
469
  OPTIONAL MATCH (peer:Node {uuid: property_diff.value})
427
470
  WHERE property_diff.property_type IN ["HAS_SOURCE", "HAS_OWNER"]
428
- AND (peer_db_id IS NULL OR %(id_func)s(peer) = peer_db_id)
471
+ AND (peer_db_id IS NULL OR elementId(peer) = peer_db_id)
429
472
  // ------------------------------
430
- // the serialized diff might not include the values for IS_VISIBLE and IS_PROTECTED in
473
+ // the serialized diff might not include the values for IS_PROTECTED in
431
474
  // some cases, so we need to figure them out here
432
475
  // ------------------------------
433
476
  CALL (attr_rel, property_diff) {
434
477
  OPTIONAL MATCH (attr_rel)-[r_vis_pro]->(bool:Boolean)
435
- WHERE property_diff.property_type IN ["IS_VISIBLE", "IS_PROTECTED"]
478
+ WHERE property_diff.property_type = "IS_PROTECTED"
436
479
  AND r_vis_pro.branch IN [$source_branch, $target_branch]
437
480
  AND type(r_vis_pro) = property_diff.property_type
438
481
  AND (property_diff.value IS NULL OR bool.value = property_diff.value)
@@ -464,15 +507,25 @@ CALL (attr_rel_prop_diff, node_db_id, peer_db_id) {
464
507
  ELSE NULL
465
508
  END as prop_rel_status
466
509
  // ------------------------------
510
+ // get from_user_id from source branch property edge
511
+ // ------------------------------
512
+ CALL (attr_rel, prop_type) {
513
+ OPTIONAL MATCH (attr_rel)-[source_prop_edge {branch: $source_branch}]->()
514
+ WHERE type(source_prop_edge) = prop_type
515
+ AND source_prop_edge.from <= $at AND source_prop_edge.to IS NULL
516
+ RETURN source_prop_edge.from_user_id AS prop_source_from_user_id
517
+ ORDER BY source_prop_edge.from DESC
518
+ LIMIT 1
519
+ }
520
+ // ------------------------------
467
521
  // set property edge.to, optionally, on target branch
468
522
  // ------------------------------
469
- CALL (attr_rel, prop_rel_status, prop_type) {
470
- OPTIONAL MATCH (attr_rel)
471
- -[target_r_prop {branch: $target_branch}]
472
- ->()
523
+ CALL (attr_rel, prop_type, prop_node, prop_source_from_user_id) {
524
+ MATCH (attr_rel)-[target_r_prop {branch: $target_branch}]->(target_prop)
473
525
  WHERE type(target_r_prop) = prop_type
526
+ AND target_prop <> prop_node
474
527
  AND target_r_prop.from < $at AND target_r_prop.to IS NULL
475
- SET target_r_prop.to = $at
528
+ SET target_r_prop.to = $at, target_r_prop.to_user_id = prop_source_from_user_id
476
529
  }
477
530
  // ------------------------------
478
531
  // check for existing edge on target_branch
@@ -485,40 +538,36 @@ CALL (attr_rel_prop_diff, node_db_id, peer_db_id) {
485
538
  AND (r_prop.to > $at OR r_prop.to IS NULL)
486
539
  RETURN r_prop
487
540
  }
488
- WITH attr_rel,prop_rel_status, prop_type, prop_node, r_prop
541
+ WITH attr_rel, prop_rel_status, prop_type, prop_node, r_prop, prop_source_from_user_id
489
542
  WHERE r_prop IS NULL
543
+ WITH attr_rel, prop_rel_status, prop_type, prop_node, prop_source_from_user_id
490
544
  // ------------------------------
491
545
  // create new edge to prop_node on target_branch, if necessary
492
546
  // one subquery per possible edge type b/c edge type cannot be a variable
493
547
  // ------------------------------
494
- CALL (attr_rel, prop_rel_status, prop_type, prop_node) {
495
- WITH attr_rel, prop_rel_status, prop_type, prop_node
548
+ CALL (attr_rel, prop_rel_status, prop_type, prop_node, prop_source_from_user_id) {
549
+ WITH attr_rel, prop_rel_status, prop_type, prop_node, prop_source_from_user_id
496
550
  WHERE prop_type = "HAS_VALUE"
497
- CREATE (attr_rel)-[:HAS_VALUE { branch: $target_branch, branch_level: $branch_level, from: $at, status: prop_rel_status }]->(prop_node)
551
+ CREATE (attr_rel)-[:HAS_VALUE { branch: $target_branch, branch_level: $branch_level, from: $at, status: prop_rel_status, from_user_id: prop_source_from_user_id }]->(prop_node)
498
552
  }
499
- CALL (attr_rel, prop_rel_status, prop_type, prop_node) {
500
- WITH attr_rel, prop_rel_status, prop_type, prop_node
553
+ CALL (attr_rel, prop_rel_status, prop_type, prop_node, prop_source_from_user_id) {
554
+ WITH attr_rel, prop_rel_status, prop_type, prop_node, prop_source_from_user_id
501
555
  WHERE prop_type = "HAS_SOURCE"
502
- CREATE (attr_rel)-[:HAS_SOURCE { branch: $target_branch, branch_level: $branch_level, from: $at, status: prop_rel_status }]->(prop_node)
556
+ CREATE (attr_rel)-[:HAS_SOURCE { branch: $target_branch, branch_level: $branch_level, from: $at, status: prop_rel_status, from_user_id: prop_source_from_user_id }]->(prop_node)
503
557
  }
504
- CALL (attr_rel, prop_rel_status, prop_type, prop_node) {
505
- WITH attr_rel, prop_rel_status, prop_type, prop_node
558
+ CALL (attr_rel, prop_rel_status, prop_type, prop_node, prop_source_from_user_id) {
559
+ WITH attr_rel, prop_rel_status, prop_type, prop_node, prop_source_from_user_id
506
560
  WHERE prop_type = "HAS_OWNER"
507
- CREATE (attr_rel)-[:HAS_OWNER { branch: $target_branch, branch_level: $branch_level, from: $at, status: prop_rel_status }]->(prop_node)
508
- }
509
- CALL (attr_rel, prop_rel_status, prop_type, prop_node) {
510
- WITH attr_rel, prop_rel_status, prop_type, prop_node
511
- WHERE prop_type = "IS_VISIBLE"
512
- CREATE (attr_rel)-[:IS_VISIBLE { branch: $target_branch, branch_level: $branch_level, from: $at, status: prop_rel_status }]->(prop_node)
561
+ CREATE (attr_rel)-[:HAS_OWNER { branch: $target_branch, branch_level: $branch_level, from: $at, status: prop_rel_status, from_user_id: prop_source_from_user_id }]->(prop_node)
513
562
  }
514
- CALL (attr_rel, prop_rel_status, prop_type, prop_node) {
515
- WITH attr_rel, prop_rel_status, prop_type, prop_node
563
+ CALL (attr_rel, prop_rel_status, prop_type, prop_node, prop_source_from_user_id) {
564
+ WITH attr_rel, prop_rel_status, prop_type, prop_node, prop_source_from_user_id
516
565
  WHERE prop_type = "IS_PROTECTED"
517
- CREATE (attr_rel)-[:IS_PROTECTED { branch: $target_branch, branch_level: $branch_level, from: $at, status: prop_rel_status }]->(prop_node)
566
+ CREATE (attr_rel)-[:IS_PROTECTED { branch: $target_branch, branch_level: $branch_level, from: $at, status: prop_rel_status, from_user_id: prop_source_from_user_id }]->(prop_node)
518
567
  }
519
568
  }
520
569
  }
521
- """ % {"id_func": db.get_id_function_name()}
570
+ """
522
571
  self.add_to_query(query=query)
523
572
 
524
573
 
@@ -582,47 +631,62 @@ CALL (n) {
582
631
  // --------------
583
632
  // ignore edges of this type that already have the correct status on the target branch
584
633
  // --------------
585
- WITH n, peer, edge_type, latest_source_edge, latest_target_edge
634
+ WITH n, peer, edge_type, latest_source_edge, latest_target_edge, latest_source_edge.from_user_id AS user_id
586
635
  WHERE (latest_target_edge IS NULL AND latest_source_edge.status = "active")
587
636
  OR latest_source_edge.status <> latest_target_edge.status
588
- CALL (latest_source_edge, latest_target_edge) {
637
+ CALL (n, latest_source_edge, latest_target_edge, user_id) {
589
638
  // --------------
590
639
  // set the to time on active target branch edges that we are setting to deleted
640
+ // to_user_id comes from the source edge's from_user_id (user who deleted it on source branch)
591
641
  // --------------
592
- WITH latest_target_edge WHERE latest_target_edge IS NOT NULL
642
+ WITH n, latest_target_edge, user_id WHERE latest_target_edge IS NOT NULL
593
643
  AND latest_source_edge.status = "deleted"
594
644
  AND latest_target_edge.status = "active"
595
645
  AND latest_target_edge.to IS NULL
596
- SET latest_target_edge.to = $at
646
+ SET latest_target_edge.to = $at, latest_target_edge.to_user_id = user_id
597
647
  }
598
648
  // --------------
649
+ // get the earliest created time to handle migrated kind/inheritance Nodes
650
+ // --------------
651
+ CALL (n) {
652
+ MATCH (earliest_n:Node {uuid: n.uuid})
653
+ RETURN earliest_n.created_at AS node_created_at, earliest_n.created_by AS node_created_by
654
+ ORDER BY earliest_n.created_at ASC
655
+ LIMIT 1
656
+ }
657
+ WITH *, COALESCE(node_created_at, $at) AS node_created_at, COALESCE(node_created_by, user_id) AS node_created_by
658
+ // --------------
599
659
  // create the outbound edges on the target branch, one subquery per possible type
660
+ // from_user_id is copied from source edge via properties(latest_source_edge)
600
661
  // --------------
601
- CALL (n, latest_source_edge, peer, edge_type) {
662
+ CALL (n, latest_source_edge, peer, edge_type, node_created_at, node_created_by) {
602
663
  WITH edge_type WHERE edge_type = "IS_PART_OF"
603
664
  CREATE (n)-[new_edge:IS_PART_OF]->(peer)
604
665
  SET new_edge = properties(latest_source_edge)
605
- SET new_edge.from = $at
606
- SET new_edge.branch_level = $branch_level
607
- SET new_edge.branch = $target_branch
666
+ SET new_edge.from = $at, new_edge.branch_level = $branch_level, new_edge.branch = $target_branch
667
+ WITH n
668
+ WHERE n.created_at IS NULL
669
+ SET n.created_at = node_created_at, n.created_by = node_created_by
608
670
  }
609
- CALL (n, latest_source_edge, peer, edge_type) {
610
- WITH edge_type
671
+ CALL (n, latest_source_edge, peer, edge_type, user_id) {
672
+ WITH peer, user_id, edge_type
611
673
  WHERE edge_type = "IS_RELATED"
612
674
  CREATE (n)-[new_edge:IS_RELATED]->(peer)
613
675
  SET new_edge = properties(latest_source_edge)
614
- SET new_edge.from = $at
615
- SET new_edge.branch_level = $branch_level
616
- SET new_edge.branch = $target_branch
676
+ SET new_edge.from = $at, new_edge.branch_level = $branch_level, new_edge.branch = $target_branch
677
+ WITH peer
678
+ WHERE peer.created_at IS NULL
679
+ SET peer.created_at = $at, peer.created_by = user_id
617
680
  }
618
- CALL (n, latest_source_edge, peer, edge_type) {
619
- WITH edge_type
681
+ CALL (n, latest_source_edge, peer, edge_type, user_id) {
682
+ WITH peer, user_id, edge_type
620
683
  WHERE edge_type = "HAS_ATTRIBUTE"
621
684
  CREATE (n)-[new_edge:HAS_ATTRIBUTE]->(peer)
622
685
  SET new_edge = properties(latest_source_edge)
623
- SET new_edge.from = $at
624
- SET new_edge.branch_level = $branch_level
625
- SET new_edge.branch = $target_branch
686
+ SET new_edge.from = $at, new_edge.branch_level = $branch_level, new_edge.branch = $target_branch
687
+ WITH peer
688
+ WHERE peer.created_at IS NULL
689
+ SET peer.created_at = $at, peer.created_by = user_id
626
690
  }
627
691
  // --------------
628
692
  // do all of this again for inbound edges
@@ -652,54 +716,202 @@ CALL (n) {
652
716
  // --------------
653
717
  // ignore edges of this type that already have the correct status on the target branch
654
718
  // --------------
655
- WITH n, peer, edge_type, latest_source_edge, latest_target_edge
719
+ WITH n, peer, edge_type, latest_source_edge, latest_target_edge, latest_source_edge.from_user_id AS user_id
656
720
  WHERE latest_target_edge IS NULL OR latest_source_edge.status <> latest_target_edge.status
657
- CALL (latest_source_edge, latest_target_edge) {
721
+ CALL (latest_source_edge, latest_target_edge, user_id, peer) {
658
722
  // --------------
659
723
  // set the to time on active target branch edges that we are setting to deleted
724
+ // to_user_id comes from the source edge's from_user_id (user who deleted it on source branch)
660
725
  // --------------
661
- WITH latest_target_edge
726
+ WITH latest_target_edge, user_id, peer
662
727
  WHERE latest_target_edge IS NOT NULL
663
728
  AND latest_source_edge.status = "deleted"
664
729
  AND latest_target_edge.status = "active"
665
730
  AND latest_target_edge.to IS NULL
666
- SET latest_target_edge.to = $at
731
+ SET latest_target_edge.to = $at, latest_target_edge.to_user_id = user_id
667
732
  }
668
733
  // --------------
669
- // create the outbound edges on the target branch, one subquery per possible type
734
+ // create the inbound edges on the target branch, one subquery per possible type
735
+ // from_user_id is copied from source edge via properties(latest_source_edge)
670
736
  // --------------
671
- CALL (n, latest_source_edge, peer, edge_type) {
672
- WITH edge_type
737
+ CALL (n, latest_source_edge, peer, edge_type, user_id) {
738
+ WITH peer, user_id, edge_type
673
739
  WHERE edge_type = "IS_RELATED"
674
740
  CREATE (n)<-[new_edge:IS_RELATED]-(peer)
675
741
  SET new_edge = properties(latest_source_edge)
676
- SET new_edge.from = $at
677
- SET new_edge.branch_level = $branch_level
678
- SET new_edge.branch = $target_branch
742
+ SET new_edge.from = $at, new_edge.branch_level = $branch_level, new_edge.branch = $target_branch
743
+ WITH peer
744
+ WHERE peer.created_at IS NULL
745
+ SET peer.created_at = $at, peer.created_by = user_id
679
746
  }
680
- CALL (n, latest_source_edge, peer, edge_type) {
681
- WITH edge_type
747
+ CALL (n, latest_source_edge, peer, edge_type, user_id) {
748
+ WITH peer, user_id, edge_type
682
749
  WHERE edge_type = "HAS_OWNER"
683
750
  CREATE (n)<-[new_edge:HAS_OWNER]-(peer)
684
751
  SET new_edge = properties(latest_source_edge)
685
- SET new_edge.from = $at
686
- SET new_edge.branch_level = $branch_level
687
- SET new_edge.branch = $target_branch
752
+ SET new_edge.from = $at, new_edge.branch_level = $branch_level, new_edge.branch = $target_branch
688
753
  }
689
- CALL (n, latest_source_edge, peer, edge_type) {
690
- WITH edge_type
754
+ CALL (n, latest_source_edge, peer, edge_type, user_id) {
755
+ WITH peer, user_id, edge_type
691
756
  WHERE edge_type = "HAS_SOURCE"
692
757
  CREATE (n)<-[new_edge:HAS_SOURCE]-(peer)
693
758
  SET new_edge = properties(latest_source_edge)
694
- SET new_edge.from = $at
695
- SET new_edge.branch_level = $branch_level
696
- SET new_edge.branch = $target_branch
759
+ SET new_edge.from = $at, new_edge.branch_level = $branch_level, new_edge.branch = $target_branch
697
760
  }
698
761
  }
699
762
  """
700
763
  self.add_to_query(query)
701
764
 
702
765
 
766
+ class DiffMergeMetadataQuery(Query):
767
+ """Set metadata properties on Nodes, Attributes, and Relationships included in this merge
768
+
769
+ Each Node, Attribute, and Relationship should have its current updated_at/by (if it exists) saved in the
770
+ previous_updated_at/by properties to support a rollback.
771
+ For Attributes and Relationships (let's call them fields), set updated_by to the user_id of the latest change on
772
+ the field on the branch being merged
773
+ For Nodes, set the updated_by to the user_id of the latest change on any associated fields on this branch
774
+ For Nodes, Attributes, and Relationships, set updated_at to the merge time.
775
+
776
+ The logic in pseudocode
777
+ For each Node we care about:
778
+ a. For each Attribute/Relationship (let's call them "fields") linked to the Node
779
+ i. filter to only fields updated on the source branch
780
+ ii. previous_updated_at/by = updated_at/by
781
+ iii. identify the latest update and set updated_by = the associated user_id
782
+ iv. set updated_at = $at
783
+ b. set Node.updated_by to the user_id associated with the latest change of ALL the Node's fields
784
+ c. set Node.updated_at = $at
785
+ """
786
+
787
+ name = "merge_metadata"
788
+ type = QueryType.WRITE
789
+ insert_return = False
790
+
791
+ def __init__(
792
+ self,
793
+ node_uuids: list[str],
794
+ at: Timestamp,
795
+ target_branch: Branch,
796
+ **kwargs: Any,
797
+ ) -> None:
798
+ super().__init__(**kwargs)
799
+ self.node_uuids = node_uuids
800
+ self.at = at
801
+ self.target_branch = target_branch
802
+
803
+ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
804
+ self.params = {
805
+ "node_uuids": self.node_uuids,
806
+ "at": self.at.to_string(),
807
+ "target_branch": self.target_branch.name,
808
+ "source_branch": self.branch.name,
809
+ "global_branch": GLOBAL_BRANCH_NAME,
810
+ "branched_from": self.branch.get_branched_from(),
811
+ }
812
+ # ruff: noqa: E501
813
+ query = """
814
+ // --------------------
815
+ // Match all affected nodes, accounting for nodes with migrated kind
816
+ // for each UUID, get the latest Node that was active on this branch
817
+ // --------------------
818
+ UNWIND $node_uuids AS node_uuid
819
+ CALL (node_uuid) {
820
+ MATCH (n:Node {uuid: node_uuid})-[e:IS_PART_OF]->(:Root)
821
+ WHERE e.branch IN [$target_branch, $global_branch]
822
+ AND (
823
+ (e.branch = $target_branch AND (e.from <= $branched_from OR e.from = $at))
824
+ OR (e.branch = $global_branch AND e.from <= $at)
825
+ )
826
+ RETURN n, e AS is_part_of_e
827
+ ORDER BY e.from DESC, e.status ASC
828
+ LIMIT 1
829
+ }
830
+ // --------------------
831
+ // Special handling for the new version of a migrated kind/inheritance Node
832
+ // set updated_at/by to the time/user that created the new version of the Node
833
+ // --------------------
834
+ CALL (n, is_part_of_e) {
835
+ WITH n, is_part_of_e
836
+ WHERE n.updated_at IS NULL
837
+ SET n.updated_at = is_part_of_e.from, n.updated_by = is_part_of_e.from_user_id
838
+ }
839
+ // --------------------
840
+ // Get all the Attributes and Relationships for this Node that were active on this branch at some point
841
+ // --------------------
842
+ MATCH (n)-[e:HAS_ATTRIBUTE|IS_RELATED]-(field:Attribute|Relationship)
843
+ WHERE e.branch IN [$source_branch, $target_branch, $global_branch]
844
+ AND (
845
+ (e.branch = $target_branch AND e.from <= $branched_from)
846
+ OR (e.branch IN [$source_branch, $global_branch] AND e.from <= $at)
847
+ )
848
+
849
+ // --------------------
850
+ // For each field, only include it if it has an update at $at on the target branch
851
+ // to prevent updating metadata for conflicts in which the base branch version was accepted
852
+ // --------------------
853
+ WITH DISTINCT n, field
854
+ WHERE exists((field)-[{branch: $target_branch, from: $at}]-())
855
+ OR exists((field)-[{branch: $target_branch, to: $at}]-())
856
+
857
+ // --------------------
858
+ // For each changed field, find the latest time and user_id that updated it on the source branch
859
+ // Check both from (creation) and to (deletion) timestamps
860
+ // Prefer non-system users (those not starting with "__")
861
+ // --------------------
862
+ CALL (field) {
863
+ // ignore HAS_ATTRIBUTE and IS_RELATED b/c these show when an Attribute/Relationship was created/deleted
864
+ // not when it was updated
865
+ MATCH ()-[edge:!HAS_ATTRIBUTE&!IS_RELATED {branch: $source_branch}]-(field)
866
+ WHERE edge.from <= $at
867
+ // Collect both from and to timestamps as potential "change times"
868
+ WITH edge,
869
+ edge.from AS from_time,
870
+ edge.from_user_id AS from_user,
871
+ edge.to AS to_time,
872
+ edge.to_user_id AS to_user
873
+ // Create rows for each type of change
874
+ UNWIND [
875
+ CASE WHEN from_time <= $at THEN {time: from_time, user_id: from_user} ELSE NULL END,
876
+ CASE WHEN to_time IS NOT NULL AND to_time <= $at THEN {time: to_time, user_id: to_user} ELSE NULL END
877
+ ] AS change
878
+ WITH change WHERE change IS NOT NULL AND change.user_id IS NOT NULL
879
+ // Sort by time DESC, then prefer non-system users (is_system ASC puts false before true)
880
+ WITH change.user_id AS change_user_id, change.time AS change_time, change.user_id STARTS WITH "__" AS is_system
881
+ RETURN change_user_id, change_time
882
+ ORDER BY change_time DESC, is_system ASC
883
+ LIMIT 1
884
+ }
885
+
886
+ // Save current field values for rollback, then update field metadata
887
+ WITH n, field, change_user_id, change_time
888
+ WHERE change_user_id IS NOT NULL
889
+ // --------------------
890
+ // make sure not to set the previous_updated_at multiple times
891
+ // --------------------
892
+ CALL (field, change_user_id) {
893
+ WITH field
894
+ WHERE field.updated_at <> $at OR field.updated_at IS NULL
895
+ SET field.previous_updated_at = field.updated_at, field.previous_updated_by = field.updated_by
896
+ SET field.updated_at = $at, field.updated_by = change_user_id
897
+ }
898
+
899
+ // Aggregate to find the latest change across all fields for Node-level metadata
900
+ WITH n, change_user_id, change_time, change_user_id STARTS WITH "__" AS is_system
901
+ ORDER BY change_time DESC, is_system ASC
902
+ WITH n, collect(change_user_id)[0] AS node_updated_by
903
+
904
+ // ------------------------------------
905
+ // Update Node metadata with latest change across all its fields
906
+ // ------------------------------------
907
+ WITH n, node_updated_by
908
+ WHERE node_updated_by IS NOT NULL
909
+ SET n.previous_updated_at = n.updated_at, n.previous_updated_by = n.updated_by
910
+ SET n.updated_at = $at, n.updated_by = node_updated_by
911
+ """
912
+ self.add_to_query(query=query)
913
+
914
+
703
915
  class DiffMergeRollbackQuery(Query):
704
916
  name = "diff_merge_rollback"
705
917
  type = QueryType.WRITE
@@ -709,33 +921,62 @@ class DiffMergeRollbackQuery(Query):
709
921
  self,
710
922
  at: Timestamp,
711
923
  target_branch: Branch,
924
+ node_uuids: list[str],
712
925
  **kwargs: Any,
713
926
  ) -> None:
714
927
  super().__init__(**kwargs)
715
928
  self.at = at
716
929
  self.target_branch = target_branch
717
930
  self.source_branch_name = self.branch.name
931
+ self.node_uuids = node_uuids
718
932
 
719
933
  async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
720
934
  self.params = {
721
935
  "at": self.at.to_string(),
722
936
  "target_branch": self.target_branch.name,
723
937
  "source_branch": self.source_branch_name,
938
+ "node_uuids": self.node_uuids,
724
939
  }
725
940
  query = """
726
- // ---------------------------
727
- // reset to times on target branch
728
- // ---------------------------
729
- CALL () {
730
- OPTIONAL MATCH ()-[r_to {to: $at, branch: $target_branch}]-()
731
- SET r_to.to = NULL
732
- }
733
- // ---------------------------
734
- // reset from times on target branch
735
- // ---------------------------
736
- CALL () {
737
- OPTIONAL MATCH ()-[r_from {from: $at, branch: $target_branch}]-()
738
- DELETE r_from
739
- }
941
+ // ---------------------------
942
+ // Restore updated_at/by from previous_* on affected Node vertices
943
+ // ---------------------------
944
+ MATCH (n:Node)
945
+ WHERE n.uuid IN $node_uuids
946
+ CALL (n) {
947
+ WITH n
948
+ WHERE n.previous_updated_at IS NOT NULL
949
+ SET n.updated_at = n.previous_updated_at, n.updated_by = n.previous_updated_by
950
+ SET n.previous_updated_at = NULL, n.previous_updated_by = NULL
951
+ }
952
+ // ---------------------------
953
+ // Restore updated_at/by from previous_* on affected Attribute/Relationship vertices
954
+ // ---------------------------
955
+ CALL (n) {
956
+ MATCH (n)-[:HAS_ATTRIBUTE|IS_RELATED {branch: $target_branch}]-(attr_rel:Attribute|Relationship)
957
+ WHERE attr_rel.previous_updated_at IS NOT NULL
958
+ WITH DISTINCT attr_rel
959
+ SET attr_rel.updated_at = attr_rel.previous_updated_at, attr_rel.updated_by = attr_rel.previous_updated_by
960
+ SET attr_rel.previous_updated_at = NULL, attr_rel.previous_updated_by = NULL
961
+ }
962
+ // ---------------------------
963
+ // Limit results to 1 row
964
+ // ---------------------------
965
+ WITH 1 AS one
966
+ LIMIT 1
967
+ // ---------------------------
968
+ // reset to times on target branch
969
+ // ---------------------------
970
+ CALL () {
971
+ OPTIONAL MATCH (v)-[r_to {to: $at, branch: $target_branch}]-()
972
+ SET r_to.to = NULL, r_to.to_user_id = NULL
973
+ }
974
+ // ---------------------------
975
+ // reset from times on target branch
976
+ // ---------------------------
977
+ CALL () {
978
+ OPTIONAL MATCH (v)-[r_from {from: $at, branch: $target_branch}]-()
979
+ DELETE r_from
980
+ }
740
981
  """
741
982
  self.add_to_query(query=query)