infrahub-server 1.6.2__py3-none-any.whl → 1.7.0__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 (253) hide show
  1. infrahub/actions/tasks.py +4 -2
  2. infrahub/api/exceptions.py +2 -2
  3. infrahub/api/schema.py +3 -1
  4. infrahub/artifacts/tasks.py +1 -0
  5. infrahub/auth.py +2 -2
  6. infrahub/cli/db.py +54 -28
  7. infrahub/computed_attribute/gather.py +3 -4
  8. infrahub/computed_attribute/tasks.py +23 -6
  9. infrahub/config.py +8 -0
  10. infrahub/constants/enums.py +12 -0
  11. infrahub/core/account.py +12 -9
  12. infrahub/core/attribute.py +106 -108
  13. infrahub/core/branch/models.py +44 -71
  14. infrahub/core/branch/tasks.py +5 -3
  15. infrahub/core/changelog/diff.py +1 -20
  16. infrahub/core/changelog/models.py +0 -7
  17. infrahub/core/constants/__init__.py +17 -0
  18. infrahub/core/constants/database.py +0 -1
  19. infrahub/core/constants/schema.py +0 -1
  20. infrahub/core/convert_object_type/repository_conversion.py +3 -4
  21. infrahub/core/diff/branch_differ.py +1 -1
  22. infrahub/core/diff/conflict_transferer.py +1 -1
  23. infrahub/core/diff/data_check_synchronizer.py +4 -3
  24. infrahub/core/diff/enricher/cardinality_one.py +2 -2
  25. infrahub/core/diff/enricher/hierarchy.py +1 -1
  26. infrahub/core/diff/enricher/labels.py +1 -1
  27. infrahub/core/diff/merger/merger.py +28 -2
  28. infrahub/core/diff/merger/serializer.py +3 -10
  29. infrahub/core/diff/model/diff.py +1 -1
  30. infrahub/core/diff/query/merge.py +376 -135
  31. infrahub/core/diff/repository/repository.py +3 -1
  32. infrahub/core/graph/__init__.py +1 -1
  33. infrahub/core/graph/constraints.py +3 -3
  34. infrahub/core/graph/schema.py +2 -12
  35. infrahub/core/ipam/reconciler.py +8 -6
  36. infrahub/core/ipam/utilization.py +8 -15
  37. infrahub/core/manager.py +133 -152
  38. infrahub/core/merge.py +1 -1
  39. infrahub/core/metadata/__init__.py +0 -0
  40. infrahub/core/metadata/interface.py +37 -0
  41. infrahub/core/metadata/model.py +31 -0
  42. infrahub/core/metadata/query/__init__.py +0 -0
  43. infrahub/core/metadata/query/node_metadata.py +301 -0
  44. infrahub/core/migrations/graph/__init__.py +4 -0
  45. infrahub/core/migrations/graph/m012_convert_account_generic.py +12 -12
  46. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +7 -12
  47. infrahub/core/migrations/graph/m017_add_core_profile.py +5 -2
  48. infrahub/core/migrations/graph/m018_uniqueness_nulls.py +2 -1
  49. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +0 -10
  50. infrahub/core/migrations/graph/m020_duplicate_edges.py +0 -8
  51. infrahub/core/migrations/graph/m025_uniqueness_nulls.py +2 -1
  52. infrahub/core/migrations/graph/m026_0000_prefix_fix.py +2 -1
  53. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +0 -1
  54. infrahub/core/migrations/graph/m031_check_number_attributes.py +2 -2
  55. infrahub/core/migrations/graph/m038_redo_0000_prefix_fix.py +2 -1
  56. infrahub/core/migrations/graph/m041_deleted_dup_edges.py +1 -1
  57. infrahub/core/migrations/graph/m049_remove_is_visible_relationship.py +53 -0
  58. infrahub/core/migrations/graph/m050_backfill_vertex_metadata.py +168 -0
  59. infrahub/core/migrations/query/__init__.py +2 -2
  60. infrahub/core/migrations/query/attribute_add.py +17 -6
  61. infrahub/core/migrations/query/attribute_remove.py +19 -5
  62. infrahub/core/migrations/query/attribute_rename.py +21 -5
  63. infrahub/core/migrations/query/node_duplicate.py +19 -4
  64. infrahub/core/migrations/query/schema_attribute_update.py +1 -1
  65. infrahub/core/migrations/schema/attribute_kind_update.py +26 -6
  66. infrahub/core/migrations/schema/attribute_name_update.py +1 -1
  67. infrahub/core/migrations/schema/attribute_supports_profile.py +5 -3
  68. infrahub/core/migrations/schema/models.py +3 -0
  69. infrahub/core/migrations/schema/node_attribute_add.py +5 -2
  70. infrahub/core/migrations/schema/node_attribute_remove.py +1 -1
  71. infrahub/core/migrations/schema/node_kind_update.py +1 -1
  72. infrahub/core/migrations/schema/node_remove.py +24 -2
  73. infrahub/core/migrations/schema/tasks.py +4 -1
  74. infrahub/core/migrations/shared.py +13 -6
  75. infrahub/core/models.py +6 -6
  76. infrahub/core/node/__init__.py +157 -58
  77. infrahub/core/node/base.py +9 -5
  78. infrahub/core/node/create.py +7 -3
  79. infrahub/core/node/delete_validator.py +1 -1
  80. infrahub/core/node/standard.py +100 -14
  81. infrahub/core/order.py +30 -0
  82. infrahub/core/property.py +0 -1
  83. infrahub/core/protocols.py +1 -0
  84. infrahub/core/protocols_base.py +10 -2
  85. infrahub/core/query/__init__.py +11 -6
  86. infrahub/core/query/attribute.py +164 -49
  87. infrahub/core/query/branch.py +58 -70
  88. infrahub/core/query/delete.py +1 -1
  89. infrahub/core/query/diff.py +7 -7
  90. infrahub/core/query/ipam.py +104 -43
  91. infrahub/core/query/node.py +1072 -281
  92. infrahub/core/query/relationship.py +531 -325
  93. infrahub/core/query/resource_manager.py +107 -18
  94. infrahub/core/query/standard_node.py +25 -5
  95. infrahub/core/query/utils.py +2 -4
  96. infrahub/core/relationship/constraints/count.py +1 -1
  97. infrahub/core/relationship/constraints/peer_kind.py +1 -1
  98. infrahub/core/relationship/constraints/peer_parent.py +1 -1
  99. infrahub/core/relationship/constraints/peer_relatives.py +1 -1
  100. infrahub/core/relationship/constraints/profiles_kind.py +1 -1
  101. infrahub/core/relationship/constraints/profiles_removal.py +168 -0
  102. infrahub/core/relationship/model.py +293 -139
  103. infrahub/core/schema/attribute_parameters.py +28 -1
  104. infrahub/core/schema/attribute_schema.py +11 -17
  105. infrahub/core/schema/basenode_schema.py +3 -0
  106. infrahub/core/schema/definitions/core/__init__.py +8 -2
  107. infrahub/core/schema/definitions/core/account.py +10 -10
  108. infrahub/core/schema/definitions/core/artifact.py +14 -8
  109. infrahub/core/schema/definitions/core/check.py +10 -4
  110. infrahub/core/schema/definitions/core/generator.py +26 -6
  111. infrahub/core/schema/definitions/core/graphql_query.py +1 -1
  112. infrahub/core/schema/definitions/core/group.py +9 -2
  113. infrahub/core/schema/definitions/core/ipam.py +80 -10
  114. infrahub/core/schema/definitions/core/menu.py +41 -7
  115. infrahub/core/schema/definitions/core/permission.py +16 -2
  116. infrahub/core/schema/definitions/core/profile.py +16 -2
  117. infrahub/core/schema/definitions/core/propose_change.py +24 -4
  118. infrahub/core/schema/definitions/core/propose_change_comment.py +23 -11
  119. infrahub/core/schema/definitions/core/propose_change_validator.py +50 -21
  120. infrahub/core/schema/definitions/core/repository.py +10 -0
  121. infrahub/core/schema/definitions/core/resource_pool.py +8 -1
  122. infrahub/core/schema/definitions/core/template.py +19 -2
  123. infrahub/core/schema/definitions/core/transform.py +11 -5
  124. infrahub/core/schema/definitions/core/webhook.py +27 -9
  125. infrahub/core/schema/manager.py +63 -43
  126. infrahub/core/schema/relationship_schema.py +6 -2
  127. infrahub/core/schema/schema_branch.py +115 -11
  128. infrahub/core/task/task.py +4 -2
  129. infrahub/core/utils.py +3 -25
  130. infrahub/core/validators/aggregated_checker.py +1 -1
  131. infrahub/core/validators/attribute/choices.py +1 -1
  132. infrahub/core/validators/attribute/enum.py +1 -1
  133. infrahub/core/validators/attribute/kind.py +6 -3
  134. infrahub/core/validators/attribute/length.py +1 -1
  135. infrahub/core/validators/attribute/min_max.py +1 -1
  136. infrahub/core/validators/attribute/number_pool.py +1 -1
  137. infrahub/core/validators/attribute/optional.py +1 -1
  138. infrahub/core/validators/attribute/regex.py +1 -1
  139. infrahub/core/validators/determiner.py +3 -3
  140. infrahub/core/validators/node/attribute.py +1 -1
  141. infrahub/core/validators/node/relationship.py +1 -1
  142. infrahub/core/validators/relationship/peer.py +1 -1
  143. infrahub/database/__init__.py +4 -4
  144. infrahub/dependencies/builder/constraint/grouped/node_runner.py +2 -0
  145. infrahub/dependencies/builder/constraint/relationship_manager/profiles_removal.py +8 -0
  146. infrahub/dependencies/registry.py +2 -0
  147. infrahub/display_labels/tasks.py +12 -3
  148. infrahub/git/integrator.py +18 -18
  149. infrahub/git/tasks.py +1 -1
  150. infrahub/git/utils.py +1 -1
  151. infrahub/graphql/app.py +2 -2
  152. infrahub/graphql/constants.py +3 -0
  153. infrahub/graphql/context.py +1 -1
  154. infrahub/graphql/field_extractor.py +1 -1
  155. infrahub/graphql/initialization.py +11 -0
  156. infrahub/graphql/loaders/account.py +134 -0
  157. infrahub/graphql/loaders/node.py +5 -12
  158. infrahub/graphql/loaders/peers.py +5 -7
  159. infrahub/graphql/manager.py +175 -21
  160. infrahub/graphql/metadata.py +91 -0
  161. infrahub/graphql/mutations/account.py +6 -6
  162. infrahub/graphql/mutations/attribute.py +0 -2
  163. infrahub/graphql/mutations/branch.py +9 -5
  164. infrahub/graphql/mutations/computed_attribute.py +1 -1
  165. infrahub/graphql/mutations/display_label.py +1 -1
  166. infrahub/graphql/mutations/hfid.py +1 -1
  167. infrahub/graphql/mutations/ipam.py +4 -6
  168. infrahub/graphql/mutations/main.py +9 -4
  169. infrahub/graphql/mutations/profile.py +16 -22
  170. infrahub/graphql/mutations/proposed_change.py +4 -4
  171. infrahub/graphql/mutations/relationship.py +40 -10
  172. infrahub/graphql/mutations/repository.py +14 -12
  173. infrahub/graphql/mutations/schema.py +2 -2
  174. infrahub/graphql/order.py +14 -0
  175. infrahub/graphql/queries/branch.py +62 -6
  176. infrahub/graphql/queries/diff/tree.py +5 -5
  177. infrahub/graphql/queries/resource_manager.py +25 -24
  178. infrahub/graphql/resolvers/account_metadata.py +84 -0
  179. infrahub/graphql/resolvers/ipam.py +6 -8
  180. infrahub/graphql/resolvers/many_relationship.py +77 -35
  181. infrahub/graphql/resolvers/resolver.py +59 -14
  182. infrahub/graphql/resolvers/single_relationship.py +87 -23
  183. infrahub/graphql/subscription/graphql_query.py +2 -0
  184. infrahub/graphql/types/__init__.py +0 -1
  185. infrahub/graphql/types/attribute.py +10 -5
  186. infrahub/graphql/types/branch.py +40 -53
  187. infrahub/graphql/types/enums.py +3 -0
  188. infrahub/graphql/types/metadata.py +28 -0
  189. infrahub/graphql/types/node.py +22 -2
  190. infrahub/graphql/types/relationship.py +10 -2
  191. infrahub/graphql/types/standard_node.py +12 -7
  192. infrahub/hfid/tasks.py +12 -3
  193. infrahub/lock.py +7 -0
  194. infrahub/menu/repository.py +1 -1
  195. infrahub/patch/queries/base.py +1 -1
  196. infrahub/pools/number.py +1 -8
  197. infrahub/profiles/gather.py +56 -0
  198. infrahub/profiles/mandatory_fields_checker.py +116 -0
  199. infrahub/profiles/models.py +66 -0
  200. infrahub/profiles/node_applier.py +154 -13
  201. infrahub/profiles/queries/get_profile_data.py +143 -31
  202. infrahub/profiles/tasks.py +79 -27
  203. infrahub/profiles/triggers.py +22 -0
  204. infrahub/proposed_change/action_checker.py +1 -1
  205. infrahub/proposed_change/tasks.py +4 -1
  206. infrahub/services/__init__.py +1 -1
  207. infrahub/services/adapters/cache/nats.py +1 -1
  208. infrahub/services/adapters/cache/redis.py +7 -0
  209. infrahub/tasks/artifact.py +1 -0
  210. infrahub/transformations/tasks.py +2 -2
  211. infrahub/trigger/catalogue.py +2 -0
  212. infrahub/trigger/models.py +1 -0
  213. infrahub/trigger/setup.py +3 -3
  214. infrahub/trigger/tasks.py +3 -0
  215. infrahub/validators/tasks.py +1 -0
  216. infrahub/webhook/gather.py +1 -1
  217. infrahub/webhook/models.py +1 -1
  218. infrahub/webhook/tasks.py +23 -7
  219. infrahub/workers/dependencies.py +9 -3
  220. infrahub/workers/infrahub_async.py +13 -4
  221. infrahub/workflows/catalogue.py +19 -0
  222. infrahub_sdk/analyzer.py +2 -2
  223. infrahub_sdk/branch.py +12 -39
  224. infrahub_sdk/checks.py +4 -4
  225. infrahub_sdk/client.py +36 -0
  226. infrahub_sdk/ctl/cli_commands.py +2 -1
  227. infrahub_sdk/ctl/graphql.py +15 -4
  228. infrahub_sdk/ctl/utils.py +2 -2
  229. infrahub_sdk/enums.py +6 -0
  230. infrahub_sdk/graphql/renderers.py +21 -0
  231. infrahub_sdk/graphql/utils.py +85 -0
  232. infrahub_sdk/node/attribute.py +12 -2
  233. infrahub_sdk/node/constants.py +12 -0
  234. infrahub_sdk/node/metadata.py +69 -0
  235. infrahub_sdk/node/node.py +65 -14
  236. infrahub_sdk/node/property.py +3 -0
  237. infrahub_sdk/node/related_node.py +37 -5
  238. infrahub_sdk/node/relationship.py +18 -1
  239. infrahub_sdk/operation.py +2 -2
  240. infrahub_sdk/schema/repository.py +1 -2
  241. infrahub_sdk/transforms.py +2 -2
  242. infrahub_sdk/types.py +18 -2
  243. {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/METADATA +17 -16
  244. {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/RECORD +252 -231
  245. infrahub_testcontainers/container.py +3 -3
  246. infrahub_testcontainers/docker-compose-cluster.test.yml +7 -7
  247. infrahub_testcontainers/docker-compose.test.yml +13 -5
  248. infrahub_testcontainers/models.py +3 -3
  249. infrahub_testcontainers/performance_test.py +1 -1
  250. infrahub/graphql/models.py +0 -6
  251. {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/WHEEL +0 -0
  252. {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/entry_points.txt +0 -0
  253. {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -31,7 +31,7 @@ class AttributeQuery(Query):
31
31
  at: Timestamp | str | None = None,
32
32
  branch: Branch | None = None,
33
33
  **kwargs: Any,
34
- ):
34
+ ) -> None:
35
35
  self.attr = attr
36
36
  self.attr_id = attr_id or attr.db_id
37
37
 
@@ -48,13 +48,14 @@ class AttributeQuery(Query):
48
48
  class AttributeUpdateValueQuery(AttributeQuery):
49
49
  name = "attribute_update_value"
50
50
  type: QueryType = QueryType.WRITE
51
-
52
- raise_error_if_empty: bool = True
51
+ insert_return: bool = False
52
+ raise_error_if_empty: bool = False
53
53
 
54
54
  async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
55
55
  at = self.at or self.attr.at
56
56
 
57
57
  self.params["attr_uuid"] = self.attr.id
58
+ self.params["user_id"] = self.user_id
58
59
  self.params["branch"] = self.branch.name
59
60
  self.params["branch_level"] = self.branch.hierarchy_level
60
61
  self.params["at"] = at.to_string()
@@ -73,29 +74,46 @@ class AttributeUpdateValueQuery(AttributeQuery):
73
74
  labels.append(GraphAttributeIPNetworkNode.get_default_label())
74
75
 
75
76
  query = """
76
- MATCH (a:Attribute { uuid: $attr_uuid })
77
- MERGE (av:%(labels)s { %(props)s } )
78
- WITH av, a
79
- LIMIT 1
80
- CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "active", from: $at }]->(av)
77
+ MATCH (a:Attribute { uuid: $attr_uuid })
78
+ MERGE (av:%(labels)s { %(props)s } )
79
+ WITH av, a
80
+ LIMIT 1
81
+ // ----------
82
+ // find the existing HAS_VALUE edge, if it exists, and set the to time and user_id
83
+ // ---------
84
+ OPTIONAL MATCH (a)-[existing_active_r:%(rel_label)s { branch: $branch, status: "active" }]->()
85
+ WHERE existing_active_r.to IS NULL
86
+ SET existing_active_r.to = $at, existing_active_r.to_user_id = $user_id
87
+ WITH av, a
88
+ LIMIT 1
89
+ // ----------
90
+ // create the new HAS_VALUE edge
91
+ // ---------
92
+ CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "active", from: $at, from_user_id: $user_id }]->(av)
93
+ // ----------
94
+ // update the Attribute node with the new timestamp and user id if we are on the default or global branch
95
+ // ---------
96
+ WITH a
97
+ WHERE $branch_level = 1
98
+ LIMIT 1
99
+ SET a.updated_at = $at, a.updated_by = $user_id
81
100
  """ % {"rel_label": self.attr._rel_to_value_label, "labels": ":".join(labels), "props": ", ".join(prop_list)}
82
101
 
83
102
  self.add_to_query(query)
84
- self.return_labels = ["a", "av", "r"]
85
103
 
86
104
 
87
105
  class AttributeUpdateFlagQuery(AttributeQuery):
88
106
  name = "attribute_update_flag"
89
107
  type: QueryType = QueryType.WRITE
90
-
91
- raise_error_if_empty: bool = True
108
+ insert_return: bool = False
109
+ raise_error_if_empty: bool = False
92
110
 
93
111
  def __init__(
94
112
  self,
95
113
  flag_name: str,
96
114
  **kwargs: Any,
97
115
  ) -> None:
98
- SUPPORTED_FLAGS = ["is_visible", "is_protected"]
116
+ SUPPORTED_FLAGS = ["is_protected"]
99
117
 
100
118
  if flag_name not in SUPPORTED_FLAGS:
101
119
  raise ValueError(f"Only {SUPPORTED_FLAGS} are supported for now.")
@@ -108,6 +126,7 @@ class AttributeUpdateFlagQuery(AttributeQuery):
108
126
  at = self.at or self.attr.at
109
127
 
110
128
  self.params["attr_uuid"] = self.attr.id
129
+ self.params["user_id"] = self.user_id
111
130
  self.params["branch"] = self.branch.name
112
131
  self.params["branch_level"] = self.branch.hierarchy_level
113
132
  self.params["at"] = at.to_string()
@@ -115,27 +134,44 @@ class AttributeUpdateFlagQuery(AttributeQuery):
115
134
  self.params["flag_type"] = self.attr.get_kind()
116
135
 
117
136
  query = """
118
- MATCH (a:Attribute { uuid: $attr_uuid })
119
- MERGE (flag:Boolean { value: $flag_value })
120
- CREATE (a)-[r:%s { branch: $branch, branch_level: $branch_level, status: "active", from: $at }]->(flag)
121
- """ % self.flag_name.upper()
122
-
137
+ MATCH (a:Attribute { uuid: $attr_uuid })
138
+ MERGE (flag:Boolean { value: $flag_value })
139
+ WITH flag, a
140
+ LIMIT 1
141
+ // ----------
142
+ // find the existing property edge, if it exists, and set the to time and user_id
143
+ // ---------
144
+ OPTIONAL MATCH (a)-[existing_active_r:%(flag_type)s { branch: $branch, status: "active" }]->()
145
+ WHERE existing_active_r.to IS NULL
146
+ SET existing_active_r.to = $at, existing_active_r.to_user_id = $user_id
147
+ // ----------
148
+ // create the new property edge
149
+ // ---------
150
+ WITH a, flag
151
+ CREATE (a)-[r:%(flag_type)s { branch: $branch, branch_level: $branch_level, status: "active", from: $at, from_user_id: $user_id }]->(flag)
152
+ // ----------
153
+ // update the Attribute node with the new timestamp and user id if we are on the default or global branch
154
+ // ---------
155
+ WITH a
156
+ WHERE $branch_level = 1
157
+ LIMIT 1
158
+ SET a.updated_at = $at, a.updated_by = $user_id
159
+ """ % {"flag_type": self.flag_name.upper()}
123
160
  self.add_to_query(query)
124
- self.return_labels = ["a", "flag", "r"]
125
161
 
126
162
 
127
163
  class AttributeUpdateNodePropertyQuery(AttributeQuery):
128
164
  name = "attribute_update_node_property"
129
165
  type: QueryType = QueryType.WRITE
130
-
131
- raise_error_if_empty: bool = True
166
+ insert_return: bool = False
167
+ raise_error_if_empty: bool = False
132
168
 
133
169
  def __init__(
134
170
  self,
135
171
  prop_name: str,
136
172
  prop_id: str | None = None,
137
173
  **kwargs: Any,
138
- ):
174
+ ) -> None:
139
175
  self.prop_name = prop_name
140
176
  self.prop_id = prop_id
141
177
 
@@ -147,6 +183,7 @@ class AttributeUpdateNodePropertyQuery(AttributeQuery):
147
183
  branch_filter, branch_params = self.branch.get_query_filter_path(at=at)
148
184
  self.params.update(branch_params)
149
185
  self.params["attr_uuid"] = self.attr.id
186
+ self.params["user_id"] = self.user_id
150
187
  self.params["branch"] = self.branch.name
151
188
  self.params["branch_level"] = self.branch.hierarchy_level
152
189
  self.params["at"] = at.to_string()
@@ -176,13 +213,29 @@ class AttributeUpdateNodePropertyQuery(AttributeQuery):
176
213
  self.add_to_query(node_query)
177
214
 
178
215
  attr_query = """
179
- MATCH (a:Attribute { uuid: $attr_uuid })
180
- CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "active", from: $at }]->(np)
216
+ MATCH (a:Attribute { uuid: $attr_uuid })
217
+ // ----------
218
+ // find the existing property edge, if it exists, and set the to time and user_id
219
+ // ---------
220
+ OPTIONAL MATCH (a)-[existing_active_r:%(rel_label)s { branch: $branch, status: "active" }]->()
221
+ WHERE existing_active_r.to IS NULL
222
+ SET existing_active_r.to = $at, existing_active_r.to_user_id = $user_id
223
+ // ----------
224
+ // create the new property edge
225
+ // ---------
226
+ WITH a, np
227
+ LIMIT 1
228
+ CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "active", from: $at, from_user_id: $user_id }]->(np)
229
+ // ----------
230
+ // update the Attribute node with the new timestamp and user id if we are on the default or global branch
231
+ // ---------
232
+ WITH a
233
+ WHERE $branch_level = 1
234
+ LIMIT 1
235
+ SET a.updated_at = $at, a.updated_by = $user_id
181
236
  """ % {"rel_label": rel_label}
182
237
  self.add_to_query(attr_query)
183
238
 
184
- self.return_labels = ["a", "np", "r"]
185
-
186
239
 
187
240
  class AttributeClearNodePropertyQuery(AttributeQuery):
188
241
  name = "attribute_clear_node_property"
@@ -192,11 +245,9 @@ class AttributeClearNodePropertyQuery(AttributeQuery):
192
245
  def __init__(
193
246
  self,
194
247
  prop_name: str,
195
- prop_id: str | None = None,
196
248
  **kwargs: Any,
197
- ):
249
+ ) -> None:
198
250
  self.prop_name = prop_name
199
- self.prop_id = prop_id
200
251
 
201
252
  super().__init__(**kwargs)
202
253
 
@@ -206,15 +257,14 @@ class AttributeClearNodePropertyQuery(AttributeQuery):
206
257
  branch_filter, branch_params = self.branch.get_query_filter_path(at=at)
207
258
  self.params.update(branch_params)
208
259
  self.params["attr_uuid"] = self.attr.id
260
+ self.params["user_id"] = self.user_id
209
261
  self.params["branch"] = self.branch.name
210
262
  self.params["branch_level"] = self.branch.hierarchy_level
211
263
  self.params["at"] = at.to_string()
212
- self.params["prop_name"] = self.prop_name
213
- self.params["prop_id"] = self.prop_id
214
264
 
215
265
  rel_label = f"HAS_{self.prop_name.upper()}"
216
266
  query = """
217
- MATCH (a:Attribute { uuid: $attr_uuid })-[r:%(rel_label)s]->(np:Node { uuid: $prop_id })
267
+ MATCH (a:Attribute { uuid: $attr_uuid })-[r:%(rel_label)s]->(np:Node)
218
268
  WITH DISTINCT a, np
219
269
  CALL (a, np) {
220
270
  MATCH (a)-[r:%(rel_label)s]->(np)
@@ -228,42 +278,107 @@ WHERE property_edge.status = "active"
228
278
  CALL (property_edge) {
229
279
  WITH property_edge
230
280
  WHERE property_edge.branch = $branch
231
- SET property_edge.to = $at
281
+ SET property_edge.to = $at, property_edge.to_user_id = $user_id
232
282
  }
233
283
  CALL (a, np, property_edge) {
234
284
  WITH property_edge
235
285
  WHERE property_edge.branch_level < $branch_level
236
- CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "deleted", from: $at }]->(np)
286
+ CREATE (a)-[r:%(rel_label)s { branch: $branch, branch_level: $branch_level, status: "deleted", from: $at, from_user_id: $user_id }]->(np)
287
+ }
288
+ CALL (a) {
289
+ WITH a
290
+ WHERE $branch_level = 1
291
+ LIMIT 1
292
+ SET a.updated_at = $at, a.updated_by = $user_id
237
293
  }
238
294
  """ % {"branch_filter": branch_filter, "rel_label": rel_label}
239
295
  self.add_to_query(query)
240
296
 
241
297
 
242
- class AttributeGetQuery(AttributeQuery):
243
- name = "attribute_get"
244
- type: QueryType = QueryType.READ
298
+ class AttributeDeleteQuery(AttributeQuery):
299
+ name = "attribute_delete"
300
+ type: QueryType = QueryType.WRITE
301
+ insert_return: bool = False
245
302
 
246
303
  async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
247
304
  self.params["attr_uuid"] = self.attr.id
248
- self.params["node_uuid"] = self.attr.node.id
249
-
305
+ self.params["user_id"] = self.user_id
306
+ self.params["branch"] = self.branch.name
307
+ self.params["branch_level"] = self.branch.hierarchy_level
308
+ self.params["branched_from"] = self.branch.get_branched_from()
250
309
  self.params["at"] = self.at.to_string()
251
310
 
252
- rels_filter, rels_params = self.branch.get_query_filter_path(at=self.at.to_string())
253
- self.params.update(rels_params)
311
+ branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at)
312
+ self.params.update(branch_params)
254
313
 
255
- query = (
256
- """
257
- MATCH (a:Attribute { uuid: $attr_uuid })
258
- MATCH p = ((a)-[r2:HAS_VALUE|IS_VISIBLE|IS_PROTECTED|HAS_SOURCE|HAS_OWNER]->(ap))
259
- WHERE all(r IN relationships(p) WHERE ( %s ))
260
- """
261
- % rels_filter
262
- )
314
+ query = """
315
+ MATCH (a:Attribute { uuid: $attr_uuid })
316
+ CALL (a) {
317
+ WITH a
318
+ WHERE $branch_level = 1
319
+ LIMIT 1
320
+ SET a.updated_at = $at, a.updated_by = $user_id
321
+ }
263
322
 
323
+ UNWIND [
324
+ ["HAS_ATTRIBUTE", "in"],
325
+ ["HAS_VALUE", "out"],
326
+ ["IS_PROTECTED", "out"],
327
+ ["HAS_SOURCE", "out"],
328
+ ["HAS_OWNER", "out"]
329
+ ] AS edge_details
330
+ WITH a, edge_details[0] AS property_type, edge_details[1] AS direction
331
+ CALL (a, property_type, direction) {
332
+ MATCH (a)-[r]-(attr_peer)
333
+ WHERE type(r) = property_type
334
+ AND (
335
+ (direction = "in" AND startNode(r) = attr_peer)
336
+ OR (direction = "out" AND startNode(r) = a)
337
+ )
338
+ AND %(branch_filter)s
339
+ RETURN r AS property_edge, attr_peer
340
+ ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
341
+ LIMIT 1
342
+ }
343
+ CALL (property_edge) {
344
+ WITH property_edge
345
+ WHERE property_edge.status = "active"
346
+ AND property_edge.branch = $branch
347
+ AND property_edge.to IS NULL
348
+ SET property_edge.to = $at, property_edge.to_user_id = $user_id
349
+ }
350
+ WITH a, property_edge, property_type, attr_peer, direction
351
+ CALL (a, property_type, attr_peer, direction) {
352
+ WITH direction
353
+ WHERE direction = "out"
354
+ CREATE (a)
355
+ -[r:$(property_type) { branch: $branch, branch_level: $branch_level, status: "deleted", from: $at, from_user_id: $user_id }]
356
+ ->(attr_peer)
357
+ }
358
+ CALL (a, property_type, attr_peer, direction) {
359
+ WITH direction
360
+ WHERE direction = "in"
361
+ CREATE (a)
362
+ <-[r:$(property_type) { branch: $branch, branch_level: $branch_level, status: "deleted", from: $at, from_user_id: $user_id }]
363
+ -(attr_peer)
364
+ }
365
+ WITH CASE
366
+ WHEN property_type = "HAS_VALUE" THEN attr_peer.value
367
+ ELSE NULL
368
+ END AS property_value
369
+ WITH property_value
370
+ RETURN property_value
371
+ ORDER BY property_value ASC
372
+ LIMIT 1
373
+ """ % {"branch_filter": branch_filter}
264
374
  self.add_to_query(query)
375
+ self.return_labels = ["property_value"]
265
376
 
266
- self.return_labels = ["a", "ap", "r2"]
377
+ def get_previous_property_value(self) -> Any:
378
+ result = self.get_result()
379
+ if result:
380
+ return result.get(label="property_value")
381
+ return None
267
382
 
268
383
 
269
384
  async def default_attribute_query_filter(
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
- from infrahub import config
6
5
  from infrahub.core.branch.enums import BranchStatus
7
6
  from infrahub.core.constants import GLOBAL_BRANCH_NAME
8
7
  from infrahub.core.query import Query, QueryType
@@ -18,7 +17,7 @@ class DeleteBranchRelationshipsQuery(Query):
18
17
 
19
18
  type: QueryType = QueryType.WRITE
20
19
 
21
- def __init__(self, branch_name: str, **kwargs: Any):
20
+ def __init__(self, branch_name: str, **kwargs: Any) -> None:
22
21
  self.branch_name = branch_name
23
22
  super().__init__(**kwargs)
24
23
 
@@ -70,89 +69,78 @@ CALL (vertex_id) {
70
69
  self.add_to_query(query)
71
70
 
72
71
 
73
- class GetAllBranchInternalRelationshipQuery(Query):
74
- name: str = "get_internal_relationship"
72
+ class RebaseBranchQuery(Query):
73
+ """Rebase a branch onto the default branch by updating edge timestamps
75
74
 
76
- type: QueryType = QueryType.READ
77
- insert_return: bool = False
78
-
79
- async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
80
- query = """
81
- MATCH p = ()-[r]-()
82
- WHERE r.branch = $branch_name
83
- RETURN DISTINCT r
84
- """
85
- self.add_to_query(query=query)
86
- self.params["branch_name"] = self.branch.name
87
- self.return_labels = ["r"]
88
-
89
-
90
- class RebaseBranchUpdateRelationshipQuery(Query):
91
- name: str = "rebase_branch_update"
75
+ For every edge on this branch
76
+ if it has a from time before $at and no to time, update it to $at
77
+ if it has a to time before $at, delete the edge
78
+ if it has a to time after $at, update the from time to $at
79
+ Then delete any orphaned vertices
80
+ """
92
81
 
82
+ name: str = "rebase_branch"
93
83
  type: QueryType = QueryType.WRITE
94
-
95
- def __init__(self, ids: list[str], **kwargs: Any) -> None:
96
- self.ids = ids
97
- super().__init__(**kwargs)
84
+ insert_return: bool = False
85
+ raise_error_if_empty: bool = False
98
86
 
99
87
  async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
100
- query = """
101
- MATCH ()-[r]->()
102
- WHERE %(id_func)s(r) IN $ids
103
- SET r.from = $at
104
- SET r.conflict = NULL
105
- """ % {
106
- "id_func": db.get_id_function_name(),
107
- }
108
-
109
- self.add_to_query(query=query)
110
-
88
+ self.params["branch_name"] = self.branch.name
111
89
  self.params["at"] = self.at.to_string()
112
- self.params["ids"] = [db.to_database_id(id) for id in self.ids]
113
- self.return_labels = [f"{db.get_id_function_name()}(r)"]
114
-
115
-
116
- class RebaseBranchDeleteRelationshipQuery(Query):
117
- name: str = "rebase_branch_delete"
118
90
 
119
- type: QueryType = QueryType.WRITE
120
- insert_return: bool = False
121
-
122
- def __init__(self, ids: list[str], **kwargs: Any) -> None:
123
- self.ids = ids
124
- super().__init__(**kwargs)
91
+ query = """
92
+ // --------------
93
+ // Get all edges on this branch with their source and destination vertices
94
+ // --------------
95
+ MATCH (s)-[r]-(d)
96
+ WHERE r.branch = $branch_name
97
+ WITH DISTINCT r, s, d
98
+ WITH r, s, d,
99
+ CASE
100
+ // No `to` and `from` <= at: update
101
+ WHEN r.to IS NULL AND r.from <= $at THEN TRUE
102
+ // Has `to` and `to` < at: delete
103
+ WHEN r.to IS NOT NULL AND r.to < $at THEN FALSE
104
+ // Has `to` and `to` >= at: update
105
+ ELSE TRUE
106
+ END AS do_update
125
107
 
126
- async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
127
- if config.SETTINGS.database.db_type == config.DatabaseType.MEMGRAPH:
128
- query = """
129
- MATCH p = (s)-[r]-(d)
130
- WHERE %(id_func)s(r) IN $ids
131
- DELETE r
132
- """
133
- else:
134
- query = """
135
- MATCH p = (s)-[r]-(d)
136
- WHERE %(id_func)s(r) IN $ids
137
- DELETE r
138
- WITH *
139
- UNWIND nodes(p) AS n
140
- MATCH (n)
141
- WHERE NOT exists((n)--())
142
- DELETE n
143
- """
144
- query %= {
145
- "id_func": db.get_id_function_name(),
146
- }
108
+ // --------------
109
+ // Process updates: set from = at for relationships we're keeping
110
+ // --------------
111
+ CALL (r, do_update) {
112
+ WITH r, do_update
113
+ WHERE do_update = TRUE
114
+ SET r.from = $at
115
+ }
147
116
 
117
+ // --------------
118
+ // Delete the edges
119
+ // --------------
120
+ WITH r, s, d, do_update
121
+ WHERE do_update = FALSE
122
+ CALL (r, s, d) {
123
+ DELETE r
124
+ }
125
+ // --------------
126
+ // Clean up any orpahned nodes edges
127
+ // --------------
128
+ WITH DISTINCT s, d
129
+ UNWIND [s, d] AS n
130
+ WITH DISTINCT n
131
+ CALL (n) {
132
+ MATCH (n)
133
+ WHERE NOT exists((n)--())
134
+ DELETE n
135
+ }
136
+ """
148
137
  self.add_to_query(query=query)
149
138
 
150
- self.params["ids"] = [db.to_database_id(id) for id in self.ids]
151
-
152
139
 
153
140
  class BranchNodeGetListQuery(StandardNodeGetListQuery):
154
141
  def __init__(self, exclude_global: bool = False, **kwargs: Any) -> None:
155
142
  self.raw_filter = f"n.status <> '{BranchStatus.DELETING.value}'"
143
+
156
144
  if exclude_global:
157
145
  self.raw_filter += f" AND n.name <> '{GLOBAL_BRANCH_NAME}'"
158
146
 
@@ -11,7 +11,7 @@ class DeleteAfterTimeQuery(Query):
11
11
  insert_return: bool = False
12
12
  type: QueryType = QueryType.WRITE
13
13
 
14
- def __init__(self, timestamp: Timestamp, **kwargs: Any):
14
+ def __init__(self, timestamp: Timestamp, **kwargs: Any) -> None:
15
15
  self.timestamp = timestamp
16
16
  super().__init__(**kwargs)
17
17
 
@@ -26,7 +26,7 @@ class DiffQuery(Query):
26
26
  diff_from: Timestamp | str = None,
27
27
  diff_to: Timestamp | str = None,
28
28
  **kwargs,
29
- ):
29
+ ) -> None:
30
30
  """A diff is always in the context of a branch"""
31
31
 
32
32
  if not diff_from and branch.is_default:
@@ -59,7 +59,7 @@ class DiffCountChanges(Query):
59
59
  diff_from: Timestamp,
60
60
  diff_to: Timestamp,
61
61
  **kwargs,
62
- ):
62
+ ) -> None:
63
63
  self.branch_names = branch_names
64
64
  self.diff_from = diff_from
65
65
  self.diff_to = diff_to
@@ -77,7 +77,7 @@ class DiffCountChanges(Query):
77
77
  AND diff_rel.branch in $branch_names
78
78
  AND (
79
79
  (diff_rel.from >= $from_time AND diff_rel.from < $to_time)
80
- OR (diff_rel.to >= $to_time AND diff_rel.to < $to_time)
80
+ OR (diff_rel.to >= $from_time AND diff_rel.to < $to_time)
81
81
  )
82
82
  AND (p.branch_support = "aware" OR q.branch_support = "aware")
83
83
  WITH diff_rel.branch AS branch_name, count(*) AS num_changes
@@ -122,7 +122,7 @@ class DiffCalculationQuery(DiffQuery):
122
122
  current_node_field_specifiers: NodeFieldSpecifierMap | None = None,
123
123
  new_node_field_specifiers: NodeFieldSpecifierMap | None = None,
124
124
  **kwargs: Any,
125
- ):
125
+ ) -> None:
126
126
  self.base_branch = base_branch
127
127
  self.diff_branch_from_time = diff_branch_from_time
128
128
  self.current_node_field_specifiers = current_node_field_specifiers
@@ -335,7 +335,7 @@ CALL (p, q, diff_rel, row_from_time) {
335
335
  AND type(r_node) IN ["HAS_ATTRIBUTE", "IS_RELATED"]
336
336
  AND any(l in labels(node) WHERE l in ["Attribute", "Relationship"])
337
337
  AND node.branch_support IN [$branch_aware, $branch_agnostic]
338
- AND type(r_prop) IN ["IS_VISIBLE", "IS_PROTECTED", "HAS_SOURCE", "HAS_OWNER", "HAS_VALUE", "IS_RELATED"]
338
+ AND type(r_prop) IN ["IS_PROTECTED", "HAS_SOURCE", "HAS_OWNER", "HAS_VALUE", "IS_RELATED"]
339
339
  AND any(l in labels(prop) WHERE l in ["Boolean", "Node", "AttributeValue"])
340
340
  AND (top_diff_rel.to IS NULL OR top_diff_rel.to >= r_node.from)
341
341
  AND (r_node.to IS NULL OR r_node.to >= r_prop.from)
@@ -532,7 +532,7 @@ CALL (root, r_root, p, diff_rel, q) {
532
532
  )
533
533
  WHERE %(id_func)s(mid_r_root) = %(id_func)s(r_root)
534
534
  AND %(id_func)s(mid_diff_rel) = %(id_func)s(diff_rel)
535
- AND type(r_prop) IN ["IS_VISIBLE", "IS_PROTECTED", "HAS_SOURCE", "HAS_OWNER", "HAS_VALUE", "IS_RELATED"]
535
+ AND type(r_prop) IN ["IS_PROTECTED", "HAS_SOURCE", "HAS_OWNER", "HAS_VALUE", "IS_RELATED"]
536
536
  AND any(l in labels(prop) WHERE l in ["Boolean", "Node", "AttributeValue"])
537
537
  AND r_prop.from < $to_time AND r_prop.branch = mid_diff_rel.branch
538
538
  AND (mid_diff_rel.to IS NULL OR mid_diff_rel.to >= r_prop.from)
@@ -609,7 +609,7 @@ class DiffPropertyPathsQuery(DiffCalculationQuery):
609
609
  MATCH diff_rel_path = (root:Root)<-[r_root:IS_PART_OF]-(n:Node)-[r_node]-(p)-[diff_rel {branch: $branch_name}]->(q)
610
610
  WHERE p.branch_support = $branch_aware
611
611
  AND any(l in labels(p) WHERE l in ["Attribute", "Relationship"])
612
- AND type(diff_rel) IN ["IS_VISIBLE", "IS_PROTECTED", "HAS_SOURCE", "HAS_OWNER", "HAS_VALUE"]
612
+ AND type(diff_rel) IN ["IS_PROTECTED", "HAS_SOURCE", "HAS_OWNER", "HAS_VALUE"]
613
613
  AND any(l in labels(q) WHERE l in ["Boolean", "Node", "AttributeValue"])
614
614
  AND type(r_node) IN ["HAS_ATTRIBUTE", "IS_RELATED"]
615
615
  // node ID and field name filtering first pass