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.
- infrahub/actions/tasks.py +4 -2
- infrahub/api/exceptions.py +2 -2
- infrahub/api/schema.py +3 -1
- infrahub/artifacts/tasks.py +1 -0
- infrahub/auth.py +2 -2
- infrahub/cli/db.py +54 -28
- infrahub/computed_attribute/gather.py +3 -4
- infrahub/computed_attribute/tasks.py +23 -6
- infrahub/config.py +8 -0
- infrahub/constants/enums.py +12 -0
- infrahub/core/account.py +12 -9
- infrahub/core/attribute.py +106 -108
- infrahub/core/branch/models.py +44 -71
- infrahub/core/branch/tasks.py +5 -3
- infrahub/core/changelog/diff.py +1 -20
- infrahub/core/changelog/models.py +0 -7
- infrahub/core/constants/__init__.py +17 -0
- infrahub/core/constants/database.py +0 -1
- infrahub/core/constants/schema.py +0 -1
- infrahub/core/convert_object_type/repository_conversion.py +3 -4
- infrahub/core/diff/branch_differ.py +1 -1
- infrahub/core/diff/conflict_transferer.py +1 -1
- infrahub/core/diff/data_check_synchronizer.py +4 -3
- infrahub/core/diff/enricher/cardinality_one.py +2 -2
- infrahub/core/diff/enricher/hierarchy.py +1 -1
- infrahub/core/diff/enricher/labels.py +1 -1
- infrahub/core/diff/merger/merger.py +28 -2
- infrahub/core/diff/merger/serializer.py +3 -10
- infrahub/core/diff/model/diff.py +1 -1
- infrahub/core/diff/query/merge.py +376 -135
- infrahub/core/diff/repository/repository.py +3 -1
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/graph/constraints.py +3 -3
- infrahub/core/graph/schema.py +2 -12
- infrahub/core/ipam/reconciler.py +8 -6
- infrahub/core/ipam/utilization.py +8 -15
- infrahub/core/manager.py +133 -152
- infrahub/core/merge.py +1 -1
- infrahub/core/metadata/__init__.py +0 -0
- infrahub/core/metadata/interface.py +37 -0
- infrahub/core/metadata/model.py +31 -0
- infrahub/core/metadata/query/__init__.py +0 -0
- infrahub/core/metadata/query/node_metadata.py +301 -0
- infrahub/core/migrations/graph/__init__.py +4 -0
- infrahub/core/migrations/graph/m012_convert_account_generic.py +12 -12
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +7 -12
- infrahub/core/migrations/graph/m017_add_core_profile.py +5 -2
- infrahub/core/migrations/graph/m018_uniqueness_nulls.py +2 -1
- infrahub/core/migrations/graph/m019_restore_rels_to_time.py +0 -10
- infrahub/core/migrations/graph/m020_duplicate_edges.py +0 -8
- infrahub/core/migrations/graph/m025_uniqueness_nulls.py +2 -1
- infrahub/core/migrations/graph/m026_0000_prefix_fix.py +2 -1
- infrahub/core/migrations/graph/m029_duplicates_cleanup.py +0 -1
- infrahub/core/migrations/graph/m031_check_number_attributes.py +2 -2
- infrahub/core/migrations/graph/m038_redo_0000_prefix_fix.py +2 -1
- infrahub/core/migrations/graph/m041_deleted_dup_edges.py +1 -1
- infrahub/core/migrations/graph/m049_remove_is_visible_relationship.py +53 -0
- infrahub/core/migrations/graph/m050_backfill_vertex_metadata.py +168 -0
- infrahub/core/migrations/query/__init__.py +2 -2
- infrahub/core/migrations/query/attribute_add.py +17 -6
- infrahub/core/migrations/query/attribute_remove.py +19 -5
- infrahub/core/migrations/query/attribute_rename.py +21 -5
- infrahub/core/migrations/query/node_duplicate.py +19 -4
- infrahub/core/migrations/query/schema_attribute_update.py +1 -1
- infrahub/core/migrations/schema/attribute_kind_update.py +26 -6
- infrahub/core/migrations/schema/attribute_name_update.py +1 -1
- infrahub/core/migrations/schema/attribute_supports_profile.py +5 -3
- infrahub/core/migrations/schema/models.py +3 -0
- infrahub/core/migrations/schema/node_attribute_add.py +5 -2
- infrahub/core/migrations/schema/node_attribute_remove.py +1 -1
- infrahub/core/migrations/schema/node_kind_update.py +1 -1
- infrahub/core/migrations/schema/node_remove.py +24 -2
- infrahub/core/migrations/schema/tasks.py +4 -1
- infrahub/core/migrations/shared.py +13 -6
- infrahub/core/models.py +6 -6
- infrahub/core/node/__init__.py +157 -58
- infrahub/core/node/base.py +9 -5
- infrahub/core/node/create.py +7 -3
- infrahub/core/node/delete_validator.py +1 -1
- infrahub/core/node/standard.py +100 -14
- infrahub/core/order.py +30 -0
- infrahub/core/property.py +0 -1
- infrahub/core/protocols.py +1 -0
- infrahub/core/protocols_base.py +10 -2
- infrahub/core/query/__init__.py +11 -6
- infrahub/core/query/attribute.py +164 -49
- infrahub/core/query/branch.py +58 -70
- infrahub/core/query/delete.py +1 -1
- infrahub/core/query/diff.py +7 -7
- infrahub/core/query/ipam.py +104 -43
- infrahub/core/query/node.py +1072 -281
- infrahub/core/query/relationship.py +531 -325
- infrahub/core/query/resource_manager.py +107 -18
- infrahub/core/query/standard_node.py +25 -5
- infrahub/core/query/utils.py +2 -4
- infrahub/core/relationship/constraints/count.py +1 -1
- infrahub/core/relationship/constraints/peer_kind.py +1 -1
- infrahub/core/relationship/constraints/peer_parent.py +1 -1
- infrahub/core/relationship/constraints/peer_relatives.py +1 -1
- infrahub/core/relationship/constraints/profiles_kind.py +1 -1
- infrahub/core/relationship/constraints/profiles_removal.py +168 -0
- infrahub/core/relationship/model.py +293 -139
- infrahub/core/schema/attribute_parameters.py +28 -1
- infrahub/core/schema/attribute_schema.py +11 -17
- infrahub/core/schema/basenode_schema.py +3 -0
- infrahub/core/schema/definitions/core/__init__.py +8 -2
- infrahub/core/schema/definitions/core/account.py +10 -10
- infrahub/core/schema/definitions/core/artifact.py +14 -8
- infrahub/core/schema/definitions/core/check.py +10 -4
- infrahub/core/schema/definitions/core/generator.py +26 -6
- infrahub/core/schema/definitions/core/graphql_query.py +1 -1
- infrahub/core/schema/definitions/core/group.py +9 -2
- infrahub/core/schema/definitions/core/ipam.py +80 -10
- infrahub/core/schema/definitions/core/menu.py +41 -7
- infrahub/core/schema/definitions/core/permission.py +16 -2
- infrahub/core/schema/definitions/core/profile.py +16 -2
- infrahub/core/schema/definitions/core/propose_change.py +24 -4
- infrahub/core/schema/definitions/core/propose_change_comment.py +23 -11
- infrahub/core/schema/definitions/core/propose_change_validator.py +50 -21
- infrahub/core/schema/definitions/core/repository.py +10 -0
- infrahub/core/schema/definitions/core/resource_pool.py +8 -1
- infrahub/core/schema/definitions/core/template.py +19 -2
- infrahub/core/schema/definitions/core/transform.py +11 -5
- infrahub/core/schema/definitions/core/webhook.py +27 -9
- infrahub/core/schema/manager.py +63 -43
- infrahub/core/schema/relationship_schema.py +6 -2
- infrahub/core/schema/schema_branch.py +115 -11
- infrahub/core/task/task.py +4 -2
- infrahub/core/utils.py +3 -25
- infrahub/core/validators/aggregated_checker.py +1 -1
- infrahub/core/validators/attribute/choices.py +1 -1
- infrahub/core/validators/attribute/enum.py +1 -1
- infrahub/core/validators/attribute/kind.py +6 -3
- infrahub/core/validators/attribute/length.py +1 -1
- infrahub/core/validators/attribute/min_max.py +1 -1
- infrahub/core/validators/attribute/number_pool.py +1 -1
- infrahub/core/validators/attribute/optional.py +1 -1
- infrahub/core/validators/attribute/regex.py +1 -1
- infrahub/core/validators/determiner.py +3 -3
- infrahub/core/validators/node/attribute.py +1 -1
- infrahub/core/validators/node/relationship.py +1 -1
- infrahub/core/validators/relationship/peer.py +1 -1
- infrahub/database/__init__.py +4 -4
- infrahub/dependencies/builder/constraint/grouped/node_runner.py +2 -0
- infrahub/dependencies/builder/constraint/relationship_manager/profiles_removal.py +8 -0
- infrahub/dependencies/registry.py +2 -0
- infrahub/display_labels/tasks.py +12 -3
- infrahub/git/integrator.py +18 -18
- infrahub/git/tasks.py +1 -1
- infrahub/git/utils.py +1 -1
- infrahub/graphql/app.py +2 -2
- infrahub/graphql/constants.py +3 -0
- infrahub/graphql/context.py +1 -1
- infrahub/graphql/field_extractor.py +1 -1
- infrahub/graphql/initialization.py +11 -0
- infrahub/graphql/loaders/account.py +134 -0
- infrahub/graphql/loaders/node.py +5 -12
- infrahub/graphql/loaders/peers.py +5 -7
- infrahub/graphql/manager.py +175 -21
- infrahub/graphql/metadata.py +91 -0
- infrahub/graphql/mutations/account.py +6 -6
- infrahub/graphql/mutations/attribute.py +0 -2
- infrahub/graphql/mutations/branch.py +9 -5
- infrahub/graphql/mutations/computed_attribute.py +1 -1
- infrahub/graphql/mutations/display_label.py +1 -1
- infrahub/graphql/mutations/hfid.py +1 -1
- infrahub/graphql/mutations/ipam.py +4 -6
- infrahub/graphql/mutations/main.py +9 -4
- infrahub/graphql/mutations/profile.py +16 -22
- infrahub/graphql/mutations/proposed_change.py +4 -4
- infrahub/graphql/mutations/relationship.py +40 -10
- infrahub/graphql/mutations/repository.py +14 -12
- infrahub/graphql/mutations/schema.py +2 -2
- infrahub/graphql/order.py +14 -0
- infrahub/graphql/queries/branch.py +62 -6
- infrahub/graphql/queries/diff/tree.py +5 -5
- infrahub/graphql/queries/resource_manager.py +25 -24
- infrahub/graphql/resolvers/account_metadata.py +84 -0
- infrahub/graphql/resolvers/ipam.py +6 -8
- infrahub/graphql/resolvers/many_relationship.py +77 -35
- infrahub/graphql/resolvers/resolver.py +59 -14
- infrahub/graphql/resolvers/single_relationship.py +87 -23
- infrahub/graphql/subscription/graphql_query.py +2 -0
- infrahub/graphql/types/__init__.py +0 -1
- infrahub/graphql/types/attribute.py +10 -5
- infrahub/graphql/types/branch.py +40 -53
- infrahub/graphql/types/enums.py +3 -0
- infrahub/graphql/types/metadata.py +28 -0
- infrahub/graphql/types/node.py +22 -2
- infrahub/graphql/types/relationship.py +10 -2
- infrahub/graphql/types/standard_node.py +12 -7
- infrahub/hfid/tasks.py +12 -3
- infrahub/lock.py +7 -0
- infrahub/menu/repository.py +1 -1
- infrahub/patch/queries/base.py +1 -1
- infrahub/pools/number.py +1 -8
- infrahub/profiles/gather.py +56 -0
- infrahub/profiles/mandatory_fields_checker.py +116 -0
- infrahub/profiles/models.py +66 -0
- infrahub/profiles/node_applier.py +154 -13
- infrahub/profiles/queries/get_profile_data.py +143 -31
- infrahub/profiles/tasks.py +79 -27
- infrahub/profiles/triggers.py +22 -0
- infrahub/proposed_change/action_checker.py +1 -1
- infrahub/proposed_change/tasks.py +4 -1
- infrahub/services/__init__.py +1 -1
- infrahub/services/adapters/cache/nats.py +1 -1
- infrahub/services/adapters/cache/redis.py +7 -0
- infrahub/tasks/artifact.py +1 -0
- infrahub/transformations/tasks.py +2 -2
- infrahub/trigger/catalogue.py +2 -0
- infrahub/trigger/models.py +1 -0
- infrahub/trigger/setup.py +3 -3
- infrahub/trigger/tasks.py +3 -0
- infrahub/validators/tasks.py +1 -0
- infrahub/webhook/gather.py +1 -1
- infrahub/webhook/models.py +1 -1
- infrahub/webhook/tasks.py +23 -7
- infrahub/workers/dependencies.py +9 -3
- infrahub/workers/infrahub_async.py +13 -4
- infrahub/workflows/catalogue.py +19 -0
- infrahub_sdk/analyzer.py +2 -2
- infrahub_sdk/branch.py +12 -39
- infrahub_sdk/checks.py +4 -4
- infrahub_sdk/client.py +36 -0
- infrahub_sdk/ctl/cli_commands.py +2 -1
- infrahub_sdk/ctl/graphql.py +15 -4
- infrahub_sdk/ctl/utils.py +2 -2
- infrahub_sdk/enums.py +6 -0
- infrahub_sdk/graphql/renderers.py +21 -0
- infrahub_sdk/graphql/utils.py +85 -0
- infrahub_sdk/node/attribute.py +12 -2
- infrahub_sdk/node/constants.py +12 -0
- infrahub_sdk/node/metadata.py +69 -0
- infrahub_sdk/node/node.py +65 -14
- infrahub_sdk/node/property.py +3 -0
- infrahub_sdk/node/related_node.py +37 -5
- infrahub_sdk/node/relationship.py +18 -1
- infrahub_sdk/operation.py +2 -2
- infrahub_sdk/schema/repository.py +1 -2
- infrahub_sdk/transforms.py +2 -2
- infrahub_sdk/types.py +18 -2
- {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/METADATA +17 -16
- {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/RECORD +252 -231
- infrahub_testcontainers/container.py +3 -3
- infrahub_testcontainers/docker-compose-cluster.test.yml +7 -7
- infrahub_testcontainers/docker-compose.test.yml +13 -5
- infrahub_testcontainers/models.py +3 -3
- infrahub_testcontainers/performance_test.py +1 -1
- infrahub/graphql/models.py +0 -6
- {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/entry_points.txt +0 -0
- {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -12,9 +12,9 @@ from infrahub.core.changelog.models import (
|
|
|
12
12
|
RelationshipCardinalityManyChangelog,
|
|
13
13
|
RelationshipCardinalityOneChangelog,
|
|
14
14
|
)
|
|
15
|
-
from infrahub.core.constants import RelationshipDirection, RelationshipStatus
|
|
15
|
+
from infrahub.core.constants import InfrahubKind, MetadataOptions, RelationshipDirection, RelationshipStatus
|
|
16
16
|
from infrahub.core.constants.database import DatabaseEdgeType
|
|
17
|
-
from infrahub.core.query import Query, QueryType
|
|
17
|
+
from infrahub.core.query import Query, QueryResult, QueryType
|
|
18
18
|
from infrahub.core.query.subquery import build_subquery_filter, build_subquery_order
|
|
19
19
|
from infrahub.core.timestamp import Timestamp
|
|
20
20
|
from infrahub.core.utils import extract_field_filters
|
|
@@ -91,7 +91,7 @@ class RelationshipPeerData:
|
|
|
91
91
|
properties: dict[str, FlagPropertyData | NodePropertyData]
|
|
92
92
|
"""UUID of the Relationship Node."""
|
|
93
93
|
|
|
94
|
-
rel_node_id: UUID
|
|
94
|
+
rel_node_id: UUID
|
|
95
95
|
"""UUID of the Relationship Node."""
|
|
96
96
|
|
|
97
97
|
rel_node_db_id: str | None = None
|
|
@@ -100,7 +100,13 @@ class RelationshipPeerData:
|
|
|
100
100
|
rels: list[RelData] | None = None
|
|
101
101
|
"""Both relationships pointing at this Relationship Node."""
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
created_at: Timestamp | None = None
|
|
104
|
+
created_by: str | None = None
|
|
105
|
+
updated_at: Timestamp | None = None
|
|
106
|
+
updated_by: str | None = None
|
|
107
|
+
|
|
108
|
+
is_from_profile: bool = False
|
|
109
|
+
profile_id: UUID | None = None
|
|
104
110
|
|
|
105
111
|
def rel_ids_per_branch(self) -> dict[str, list[str | int]]:
|
|
106
112
|
response = defaultdict(list)
|
|
@@ -144,7 +150,7 @@ class RelationshipQuery(Query):
|
|
|
144
150
|
def __init__(
|
|
145
151
|
self,
|
|
146
152
|
rel: type[Relationship] | Relationship | None = None,
|
|
147
|
-
|
|
153
|
+
rel_id: str | None = None,
|
|
148
154
|
source: Node | None = None,
|
|
149
155
|
source_id: UUID | None = None,
|
|
150
156
|
destination: Node | None = None,
|
|
@@ -153,13 +159,15 @@ class RelationshipQuery(Query):
|
|
|
153
159
|
branch: Branch | None = None,
|
|
154
160
|
at: Timestamp | str | None = None,
|
|
155
161
|
**kwargs,
|
|
156
|
-
):
|
|
162
|
+
) -> None:
|
|
157
163
|
if not source and not source_id:
|
|
158
164
|
raise ValueError("Either source or source_id must be provided.")
|
|
159
|
-
if not rel and not
|
|
160
|
-
raise ValueError("
|
|
161
|
-
if not inspect.isclass(rel) and not hasattr(rel, "schema"):
|
|
162
|
-
raise ValueError(
|
|
165
|
+
if not rel and not rel_id:
|
|
166
|
+
raise ValueError("rel or rel_id must be provided.")
|
|
167
|
+
if not inspect.isclass(rel) and not hasattr(rel, "schema") and not rel_id:
|
|
168
|
+
raise ValueError(
|
|
169
|
+
"Rel must be a Relationship class or an instance of Relationship or a relationship ID must be provided."
|
|
170
|
+
)
|
|
163
171
|
if not schema and inspect.isclass(rel) and not hasattr(rel, "schema"):
|
|
164
172
|
raise ValueError("Either an instance of Relationship or a valid schema must be provided.")
|
|
165
173
|
|
|
@@ -174,7 +182,7 @@ class RelationshipQuery(Query):
|
|
|
174
182
|
self.destination_id = destination.id
|
|
175
183
|
|
|
176
184
|
self.rel = rel
|
|
177
|
-
self.
|
|
185
|
+
self.rel_id = rel_id
|
|
178
186
|
self.schema = schema or self.rel.schema
|
|
179
187
|
|
|
180
188
|
if not branch and inspect.isclass(rel) and not hasattr(rel, "branch"):
|
|
@@ -191,12 +199,13 @@ class RelationshipQuery(Query):
|
|
|
191
199
|
|
|
192
200
|
super().__init__(**kwargs)
|
|
193
201
|
|
|
194
|
-
def get_relationship_properties_dict(self, status: RelationshipStatus) -> dict[str, str | None]:
|
|
202
|
+
def get_relationship_properties_dict(self, status: RelationshipStatus, user_id: str) -> dict[str, str | None]:
|
|
195
203
|
rel_prop_dict = {
|
|
196
204
|
"branch": self.branch.name,
|
|
197
205
|
"branch_level": self.branch.hierarchy_level,
|
|
198
206
|
"status": status.value,
|
|
199
207
|
"from": self.at.to_string(),
|
|
208
|
+
"from_user_id": user_id,
|
|
200
209
|
}
|
|
201
210
|
if self.schema.hierarchical:
|
|
202
211
|
rel_prop_dict["hierarchy"] = self.schema.hierarchical
|
|
@@ -206,13 +215,12 @@ class RelationshipQuery(Query):
|
|
|
206
215
|
self.params["source_id"] = self.source_id or self.source.get_id()
|
|
207
216
|
if source_branch.is_global or source_branch.is_default:
|
|
208
217
|
source_query_match = """
|
|
209
|
-
MATCH (s:Node { uuid: $source_id })-[source_e:IS_PART_OF {
|
|
218
|
+
MATCH (s:Node { uuid: $source_id })-[source_e:IS_PART_OF {branch_level: 1, status: "active"}]->(:Root)
|
|
210
219
|
WHERE source_e.from <= $at AND (source_e.to IS NULL OR source_e.to > $at)
|
|
211
|
-
OPTIONAL MATCH (s)-[delete_edge:IS_PART_OF {status: "deleted",
|
|
220
|
+
OPTIONAL MATCH (s)-[delete_edge:IS_PART_OF {status: "deleted", branch_level: 1}]->(:Root)
|
|
212
221
|
WHERE delete_edge.from <= $at
|
|
213
222
|
WITH *, s WHERE delete_edge IS NULL
|
|
214
223
|
"""
|
|
215
|
-
self.params["source_branch"] = source_branch.name
|
|
216
224
|
else:
|
|
217
225
|
source_filter, source_filter_params = source_branch.get_query_filter_path(
|
|
218
226
|
at=self.at, variable_name="r", params_prefix="src_"
|
|
@@ -235,13 +243,12 @@ class RelationshipQuery(Query):
|
|
|
235
243
|
self.params["destination_id"] = destination_id
|
|
236
244
|
if destination_branch.is_global or destination_branch.is_default:
|
|
237
245
|
destination_query_match = """
|
|
238
|
-
MATCH (d:Node { uuid: $destination_id })-[dest_e:IS_PART_OF {
|
|
246
|
+
MATCH (d:Node { uuid: $destination_id })-[dest_e:IS_PART_OF {branch_level: 1, status: "active"}]->(:Root)
|
|
239
247
|
WHERE dest_e.from <= $at AND (dest_e.to IS NULL OR dest_e.to > $at)
|
|
240
|
-
OPTIONAL MATCH (d)-[delete_edge:IS_PART_OF {status: "deleted",
|
|
248
|
+
OPTIONAL MATCH (d)-[delete_edge:IS_PART_OF {status: "deleted", branch_level: 1}]->(:Root)
|
|
241
249
|
WHERE delete_edge.from <= $at
|
|
242
250
|
WITH *, d WHERE delete_edge IS NULL
|
|
243
251
|
"""
|
|
244
|
-
self.params["destination_branch"] = destination_branch.name
|
|
245
252
|
else:
|
|
246
253
|
destination_filter, destination_filter_params = destination_branch.get_query_filter_path(
|
|
247
254
|
at=self.at, variable_name="r", params_prefix="dst_"
|
|
@@ -271,7 +278,7 @@ class RelationshipCreateQuery(RelationshipQuery):
|
|
|
271
278
|
destination: Node = None,
|
|
272
279
|
destination_id: UUID | None = None,
|
|
273
280
|
**kwargs,
|
|
274
|
-
):
|
|
281
|
+
) -> None:
|
|
275
282
|
if not destination and not destination_id:
|
|
276
283
|
raise ValueError("Either destination or destination_id must be provided.")
|
|
277
284
|
|
|
@@ -288,7 +295,7 @@ class RelationshipCreateQuery(RelationshipQuery):
|
|
|
288
295
|
self.params["at"] = self.at.to_string()
|
|
289
296
|
|
|
290
297
|
self.params["is_protected"] = self.rel.is_protected
|
|
291
|
-
self.params["
|
|
298
|
+
self.params["user_id"] = self.user_id
|
|
292
299
|
|
|
293
300
|
self.add_source_match_to_query(source_branch=self.source.get_branch_based_on_support_type())
|
|
294
301
|
self.add_dest_match_to_query(
|
|
@@ -297,35 +304,72 @@ class RelationshipCreateQuery(RelationshipQuery):
|
|
|
297
304
|
)
|
|
298
305
|
self.query_add_all_node_property_match()
|
|
299
306
|
|
|
300
|
-
self.params["rel_prop"] = self.get_relationship_properties_dict(
|
|
307
|
+
self.params["rel_prop"] = self.get_relationship_properties_dict(
|
|
308
|
+
status=RelationshipStatus.ACTIVE, user_id=self.user_id
|
|
309
|
+
)
|
|
301
310
|
arrows = self.schema.get_query_arrows()
|
|
302
|
-
r1 = f"{arrows.left.start}[r1:
|
|
303
|
-
r2 = f"{arrows.right.start}[r2:
|
|
311
|
+
r1 = f"{arrows.left.start}[r1:IS_RELATED $rel_prop ]{arrows.left.end}"
|
|
312
|
+
r2 = f"{arrows.right.start}[r2:IS_RELATED $rel_prop ]{arrows.right.end}"
|
|
313
|
+
|
|
314
|
+
relationship_create_query = (
|
|
315
|
+
"CREATE (rl:Relationship { uuid: $uuid, name: $name, branch_support: $branch_support"
|
|
316
|
+
)
|
|
317
|
+
if self.branch.is_default or self.branch.is_global:
|
|
318
|
+
relationship_create_query += (
|
|
319
|
+
", created_at: $at, created_by: $user_id, updated_at: $at, updated_by: $user_id"
|
|
320
|
+
)
|
|
321
|
+
relationship_create_query += " })"
|
|
322
|
+
self.add_to_query(relationship_create_query)
|
|
304
323
|
|
|
305
324
|
query_create = """
|
|
306
|
-
CREATE (rl:Relationship { uuid: $uuid, name: $name, branch_support: $branch_support })
|
|
307
325
|
CREATE (s)%s(rl)
|
|
308
326
|
CREATE (rl)%s(d)
|
|
309
327
|
MERGE (ip:Boolean { value: $is_protected })
|
|
310
|
-
MERGE (iv:Boolean { value: $is_visible })
|
|
311
328
|
CREATE (rl)-[r3:IS_PROTECTED $rel_prop ]->(ip)
|
|
312
|
-
CREATE (rl)-[r4:IS_VISIBLE $rel_prop ]->(iv)
|
|
313
329
|
""" % (
|
|
314
330
|
r1,
|
|
315
331
|
r2,
|
|
316
332
|
)
|
|
333
|
+
if self.branch.is_default or self.branch.is_global:
|
|
334
|
+
query_create += """
|
|
335
|
+
SET s.updated_at = $at, s.updated_by = $user_id
|
|
336
|
+
SET d.updated_at = $at, d.updated_by = $user_id
|
|
337
|
+
"""
|
|
317
338
|
|
|
318
339
|
self.add_to_query(query_create)
|
|
319
|
-
self.return_labels = ["s", "d", "rl", "r1", "r2", "r3"
|
|
340
|
+
self.return_labels = ["s", "d", "rl", "r1", "r2", "r3"]
|
|
320
341
|
self.query_add_all_node_property_create()
|
|
321
342
|
|
|
322
343
|
def query_add_all_node_property_match(self) -> None:
|
|
344
|
+
if not self.rel._node_properties:
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at)
|
|
348
|
+
self.params.update(branch_params)
|
|
349
|
+
|
|
323
350
|
for prop_name in self.rel._node_properties:
|
|
324
351
|
if hasattr(self.rel, f"{prop_name}_id") and getattr(self.rel, f"{prop_name}_id"):
|
|
325
|
-
self.query_add_node_property_match(name=prop_name)
|
|
326
|
-
|
|
327
|
-
def query_add_node_property_match(self, name: str) -> None:
|
|
328
|
-
self.
|
|
352
|
+
self.query_add_node_property_match(name=prop_name, branch_filter=branch_filter)
|
|
353
|
+
|
|
354
|
+
def query_add_node_property_match(self, name: str, branch_filter: str) -> None:
|
|
355
|
+
if self.branch.is_default or self.branch.is_global:
|
|
356
|
+
match_property_peer_query = """
|
|
357
|
+
MATCH (%(var_name)s:Node { uuid: $prop_%(var_name)s_id })
|
|
358
|
+
WHERE NOT exists((%(var_name)s)-[:IS_PART_OF {branch: $branch, status: "deleted"}]->(:Root))
|
|
359
|
+
""" % {"var_name": name}
|
|
360
|
+
else:
|
|
361
|
+
match_property_peer_query = """
|
|
362
|
+
CALL () {
|
|
363
|
+
MATCH (%(var_name)s:Node { uuid: $prop_%(var_name)s_id })-[r:IS_PART_OF]->(:Root)
|
|
364
|
+
WHERE %(branch_filter)s
|
|
365
|
+
RETURN %(var_name)s, r.status = "active" AS %(var_name)s_is_active
|
|
366
|
+
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
367
|
+
LIMIT 1
|
|
368
|
+
}
|
|
369
|
+
WITH *
|
|
370
|
+
WHERE %(var_name)s_is_active = TRUE
|
|
371
|
+
""" % {"var_name": name, "branch_filter": branch_filter}
|
|
372
|
+
self.add_to_query(match_property_peer_query)
|
|
329
373
|
self.params[f"prop_{name}_id"] = getattr(self.rel, f"{name}_id")
|
|
330
374
|
self.return_labels.append(name)
|
|
331
375
|
|
|
@@ -336,7 +380,7 @@ class RelationshipCreateQuery(RelationshipQuery):
|
|
|
336
380
|
|
|
337
381
|
def query_add_node_property_create(self, name: str) -> None:
|
|
338
382
|
query = """
|
|
339
|
-
|
|
383
|
+
CREATE (rl)-[:HAS_%s { branch: $branch, branch_level: $branch_level, status: "active", from: $at, from_user_id: $user_id }]->(%s)
|
|
340
384
|
""" % (
|
|
341
385
|
name.upper(),
|
|
342
386
|
name,
|
|
@@ -347,15 +391,15 @@ class RelationshipCreateQuery(RelationshipQuery):
|
|
|
347
391
|
class RelationshipUpdatePropertyQuery(RelationshipQuery):
|
|
348
392
|
name = "relationship_property_update"
|
|
349
393
|
type = QueryType.WRITE
|
|
394
|
+
insert_return = False
|
|
395
|
+
raise_error_if_empty = False
|
|
350
396
|
|
|
351
397
|
def __init__(
|
|
352
398
|
self,
|
|
353
|
-
rel_node_id: str,
|
|
354
399
|
flag_properties_to_update: dict[str, bool],
|
|
355
400
|
node_properties_to_update: dict[str, str],
|
|
356
401
|
**kwargs,
|
|
357
|
-
):
|
|
358
|
-
self.rel_node_id = rel_node_id
|
|
402
|
+
) -> None:
|
|
359
403
|
if not flag_properties_to_update and not node_properties_to_update:
|
|
360
404
|
raise ValueError("Either flag_properties_to_update or node_properties_to_update must be set")
|
|
361
405
|
self.flag_properties_to_update = flag_properties_to_update
|
|
@@ -363,22 +407,75 @@ class RelationshipUpdatePropertyQuery(RelationshipQuery):
|
|
|
363
407
|
super().__init__(**kwargs)
|
|
364
408
|
|
|
365
409
|
async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG002
|
|
366
|
-
self.params["rel_node_id"] = self.
|
|
410
|
+
self.params["rel_node_id"] = self.rel_id or (self.rel.id if self.rel else None)
|
|
367
411
|
self.params["branch"] = self.branch.name
|
|
368
412
|
self.params["branch_level"] = self.branch.hierarchy_level
|
|
413
|
+
self.params["user_id"] = self.user_id
|
|
369
414
|
self.params["at"] = self.at.to_string()
|
|
370
415
|
|
|
371
|
-
|
|
416
|
+
rel_query = """
|
|
372
417
|
MATCH (rl:Relationship { uuid: $rel_node_id })
|
|
373
418
|
"""
|
|
374
|
-
self.
|
|
419
|
+
if self.branch.is_default or self.branch.is_global:
|
|
420
|
+
rel_query += """
|
|
421
|
+
SET rl.updated_at = $at, rl.updated_by = $user_id
|
|
422
|
+
WITH rl
|
|
423
|
+
"""
|
|
424
|
+
self.add_to_query(rel_query)
|
|
375
425
|
|
|
376
|
-
self.
|
|
426
|
+
self.params["property_types_to_update"] = sorted(
|
|
427
|
+
[flag.upper() for flag in self.flag_properties_to_update.keys()]
|
|
428
|
+
)
|
|
429
|
+
self.params["property_types_to_update"] += sorted(
|
|
430
|
+
[f"HAS_{prop.upper()}" for prop in self.node_properties_to_update.keys()]
|
|
431
|
+
)
|
|
432
|
+
set_to_time_on_current_property_query = """
|
|
433
|
+
OPTIONAL MATCH (rl)-[r]->()
|
|
434
|
+
WHERE type(r) IN $property_types_to_update
|
|
435
|
+
AND r.branch = $branch
|
|
436
|
+
AND r.status = "active"
|
|
437
|
+
AND r.to IS NULL
|
|
438
|
+
SET r.to = $at, r.to_user_id = $user_id
|
|
439
|
+
WITH rl
|
|
440
|
+
"""
|
|
441
|
+
self.add_to_query(set_to_time_on_current_property_query)
|
|
442
|
+
|
|
443
|
+
branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at)
|
|
444
|
+
self.params.update(branch_params)
|
|
445
|
+
|
|
446
|
+
self.query_add_all_node_property_merge(branch_filter=branch_filter)
|
|
377
447
|
self.query_add_all_flag_property_merge()
|
|
378
448
|
|
|
379
|
-
self.query_add_all_node_property_create()
|
|
449
|
+
self.query_add_all_node_property_create(branch_filter=branch_filter)
|
|
380
450
|
self.query_add_all_flag_property_create()
|
|
381
451
|
|
|
452
|
+
# Update peer node metadata at the end (only on default/global branch)
|
|
453
|
+
if self.branch.is_default or self.branch.is_global:
|
|
454
|
+
peer_metadata_query = """
|
|
455
|
+
WITH rl
|
|
456
|
+
CALL (rl) {
|
|
457
|
+
MATCH (peer:Node)-[r_rel:IS_RELATED]-(rl)
|
|
458
|
+
WHERE r_rel.branch_level = 1
|
|
459
|
+
WITH DISTINCT peer, rl
|
|
460
|
+
CALL (peer, rl) {
|
|
461
|
+
MATCH (peer)-[r_rel:IS_RELATED]-(rl)
|
|
462
|
+
WHERE r_rel.branch_level = 1
|
|
463
|
+
ORDER BY r_rel.from DESC, r_rel.status ASC
|
|
464
|
+
LIMIT 1
|
|
465
|
+
WITH peer, r_rel
|
|
466
|
+
WHERE r_rel.status = "active" AND r_rel.to IS NULL
|
|
467
|
+
MATCH (peer)-[r_part:IS_PART_OF]->(:Root)
|
|
468
|
+
WHERE r_part.branch_level = 1
|
|
469
|
+
ORDER BY r_part.from DESC, r_part.status ASC
|
|
470
|
+
LIMIT 1
|
|
471
|
+
WITH peer, r_part
|
|
472
|
+
WHERE r_part.status = "active" AND r_part.to IS NULL
|
|
473
|
+
SET peer.updated_at = $at, peer.updated_by = $user_id
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
"""
|
|
477
|
+
self.add_to_query(peer_metadata_query)
|
|
478
|
+
|
|
382
479
|
def query_add_all_flag_property_merge(self) -> None:
|
|
383
480
|
for prop_name, prop_value in self.flag_properties_to_update.items():
|
|
384
481
|
self.query_add_flag_property_merge(name=prop_name, value=prop_value)
|
|
@@ -386,34 +483,30 @@ class RelationshipUpdatePropertyQuery(RelationshipQuery):
|
|
|
386
483
|
def query_add_flag_property_merge(self, name: str, value: bool) -> None:
|
|
387
484
|
self.add_to_query("MERGE (prop_%s:Boolean { value: $prop_%s })" % (name, name))
|
|
388
485
|
self.params[f"prop_{name}"] = value
|
|
389
|
-
self.return_labels.append(f"prop_{name}")
|
|
390
|
-
|
|
391
|
-
def query_add_all_node_property_merge(self) -> None:
|
|
392
|
-
branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at)
|
|
393
|
-
self.params.update(branch_params)
|
|
394
486
|
|
|
487
|
+
def query_add_all_node_property_merge(self, branch_filter: str) -> None:
|
|
395
488
|
for prop_name, prop_value in self.node_properties_to_update.items():
|
|
396
489
|
self.params[f"prop_{prop_name}"] = prop_value
|
|
397
490
|
if self.branch.is_default or self.branch.is_global:
|
|
398
491
|
node_query = """
|
|
399
|
-
MATCH (prop_%(prop_name)s:Node {uuid: $prop_%(prop_name)s })-[r_%(prop_name)s:IS_PART_OF]->(:Root)
|
|
492
|
+
OPTIONAL MATCH (prop_%(prop_name)s:Node {uuid: $prop_%(prop_name)s })-[r_%(prop_name)s:IS_PART_OF]->(:Root)
|
|
400
493
|
WHERE r_%(prop_name)s.branch IN $branch0
|
|
401
494
|
AND r_%(prop_name)s.status = "active"
|
|
402
495
|
AND r_%(prop_name)s.from <= $at AND (r_%(prop_name)s.to IS NULL OR r_%(prop_name)s.to > $at)
|
|
403
496
|
WITH *
|
|
497
|
+
WHERE $prop_%(prop_name)s IS NULL OR prop_%(prop_name)s IS NOT NULL
|
|
404
498
|
LIMIT 1
|
|
405
499
|
""" % {"prop_name": prop_name}
|
|
406
500
|
else:
|
|
407
501
|
node_query = """
|
|
408
|
-
MATCH (prop_%(prop_name)s:Node {uuid: $prop_%(prop_name)s })-[r_%(prop_name)s:IS_PART_OF]->(:Root)
|
|
502
|
+
OPTIONAL MATCH (prop_%(prop_name)s:Node {uuid: $prop_%(prop_name)s })-[r_%(prop_name)s:IS_PART_OF]->(:Root)
|
|
409
503
|
WHERE all(r in [r_%(prop_name)s] WHERE %(branch_filter)s)
|
|
410
504
|
ORDER BY r_%(prop_name)s.branch_level DESC, r_%(prop_name)s.from DESC, r_%(prop_name)s.status ASC
|
|
411
505
|
LIMIT 1
|
|
412
506
|
WITH *
|
|
413
|
-
WHERE r_%(prop_name)s.status = "active"
|
|
507
|
+
WHERE $prop_%(prop_name)s IS NULL OR r_%(prop_name)s.status = "active"
|
|
414
508
|
""" % {"branch_filter": branch_filter, "prop_name": prop_name}
|
|
415
509
|
self.add_to_query(node_query)
|
|
416
|
-
self.return_labels.append(f"prop_{prop_name}")
|
|
417
510
|
|
|
418
511
|
def query_add_all_flag_property_create(self) -> None:
|
|
419
512
|
for prop_name in self.flag_properties_to_update:
|
|
@@ -421,172 +514,158 @@ class RelationshipUpdatePropertyQuery(RelationshipQuery):
|
|
|
421
514
|
|
|
422
515
|
def query_add_flag_property_create(self, name: str) -> None:
|
|
423
516
|
query = """
|
|
424
|
-
CREATE (rl)-[:%s { branch: $branch, branch_level: $branch_level, status: "active", from: $at }]->(prop_%s)
|
|
517
|
+
CREATE (rl)-[:%s { branch: $branch, branch_level: $branch_level, status: "active", from: $at, from_user_id: $user_id }]->(prop_%s)
|
|
425
518
|
""" % (
|
|
426
519
|
name.upper(),
|
|
427
520
|
name,
|
|
428
521
|
)
|
|
429
522
|
self.add_to_query(query)
|
|
430
523
|
|
|
431
|
-
def query_add_all_node_property_create(self) -> None:
|
|
524
|
+
def query_add_all_node_property_create(self, branch_filter: str) -> None:
|
|
432
525
|
for prop_name in self.node_properties_to_update:
|
|
433
|
-
self.query_add_node_property_create(name=prop_name)
|
|
434
|
-
|
|
435
|
-
def query_add_node_property_create(self, name: str) -> None:
|
|
436
|
-
query = """
|
|
437
|
-
CREATE (rl)-[:%s { branch: $branch, branch_level: $branch_level, status: "active", from: $at }]->(prop_%s)
|
|
438
|
-
""" % (
|
|
439
|
-
"HAS_" + name.upper(),
|
|
440
|
-
name,
|
|
441
|
-
)
|
|
442
|
-
self.add_to_query(query)
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
class RelationshipDataDeleteQuery(RelationshipQuery):
|
|
446
|
-
name = "relationship_data_delete"
|
|
447
|
-
type = QueryType.WRITE
|
|
448
|
-
|
|
449
|
-
def __init__(
|
|
450
|
-
self,
|
|
451
|
-
data: RelationshipPeerData,
|
|
452
|
-
**kwargs,
|
|
453
|
-
):
|
|
454
|
-
self.data = data
|
|
455
|
-
super().__init__(**kwargs)
|
|
526
|
+
self.query_add_node_property_create(name=prop_name, branch_filter=branch_filter)
|
|
456
527
|
|
|
457
|
-
|
|
458
|
-
self.params["source_id"] = self.source_id
|
|
459
|
-
self.params["rel_node_id"] = self.data.rel_node_id
|
|
460
|
-
self.params["name"] = self.schema.identifier
|
|
461
|
-
self.params["branch"] = self.branch.name
|
|
462
|
-
self.params["branch_level"] = self.branch.hierarchy_level
|
|
463
|
-
self.params["at"] = self.at.to_string()
|
|
464
|
-
|
|
465
|
-
# -----------------------------------------------------------------------
|
|
466
|
-
# Match all nodes, including properties
|
|
467
|
-
# -----------------------------------------------------------------------
|
|
468
|
-
|
|
469
|
-
self.add_source_match_to_query(source_branch=self.source.get_branch_based_on_support_type())
|
|
470
|
-
self.add_dest_match_to_query(destination_branch=self.branch, destination_id=self.data.peer_id)
|
|
528
|
+
def query_add_node_property_create(self, name: str, branch_filter: str) -> None:
|
|
471
529
|
query = """
|
|
472
|
-
|
|
473
|
-
|
|
530
|
+
WITH *
|
|
531
|
+
CALL (rl, prop_%(var_name)s) {
|
|
532
|
+
WITH rl, prop_%(var_name)s
|
|
533
|
+
WHERE $prop_%(var_name)s IS NOT NULL
|
|
534
|
+
CREATE (rl)
|
|
535
|
+
-[:%(edge_type)s { branch: $branch, branch_level: $branch_level, status: "active", from: $at, from_user_id: $user_id }]
|
|
536
|
+
->(prop_%(var_name)s)
|
|
537
|
+
}
|
|
538
|
+
CALL (rl) {
|
|
539
|
+
WITH rl
|
|
540
|
+
WHERE $prop_%(var_name)s IS NULL
|
|
541
|
+
AND $branch_level > 1
|
|
542
|
+
MATCH (rl)-[r:%(edge_type)s]->(current_prop)
|
|
543
|
+
WHERE %(branch_filter)s
|
|
544
|
+
AND r.branch_level = 1
|
|
545
|
+
AND r.status = "active"
|
|
546
|
+
CREATE (rl)
|
|
547
|
+
-[:%(edge_type)s { branch: $branch, branch_level: $branch_level, status: "deleted", from: $at, from_user_id: $user_id }]
|
|
548
|
+
->(current_prop)
|
|
549
|
+
}
|
|
550
|
+
""" % {"edge_type": "HAS_" + name.upper(), "var_name": name, "branch_filter": branch_filter}
|
|
474
551
|
self.add_to_query(query)
|
|
475
|
-
self.return_labels = ["s", "d", "rl"]
|
|
476
|
-
|
|
477
|
-
for prop_name, prop in self.data.properties.items():
|
|
478
|
-
self.add_to_query(
|
|
479
|
-
"MATCH (prop_%(prop_name)s) WHERE %(id_func)s(prop_%(prop_name)s) = $prop_%(prop_name)s_id"
|
|
480
|
-
% {"prop_name": prop_name, "id_func": db.get_id_function_name()}
|
|
481
|
-
)
|
|
482
|
-
self.params[f"prop_{prop_name}_id"] = db.to_database_id(prop.prop_db_id)
|
|
483
|
-
self.return_labels.append(f"prop_{prop_name}")
|
|
484
|
-
|
|
485
|
-
self.params["rel_prop"] = self.get_relationship_properties_dict(status=RelationshipStatus.DELETED)
|
|
486
|
-
|
|
487
|
-
arrows = self.schema.get_query_arrows()
|
|
488
|
-
r1 = f"{arrows.left.start}[r1:{self.rel_type} $rel_prop ]{arrows.left.end}"
|
|
489
|
-
r2 = f"{arrows.right.start}[r2:{self.rel_type} $rel_prop ]{arrows.right.end}"
|
|
490
|
-
|
|
491
|
-
# -----------------------------------------------------------------------
|
|
492
|
-
# Create all the DELETE relationships, including properties
|
|
493
|
-
# -----------------------------------------------------------------------
|
|
494
|
-
query = """
|
|
495
|
-
CREATE (s)%s(rl)
|
|
496
|
-
CREATE (rl)%s(d)
|
|
497
|
-
""" % (
|
|
498
|
-
r1,
|
|
499
|
-
r2,
|
|
500
|
-
)
|
|
501
|
-
self.add_to_query(query)
|
|
502
|
-
self.return_labels.extend(["r1", "r2"])
|
|
503
|
-
|
|
504
|
-
for prop_name, prop in self.data.properties.items():
|
|
505
|
-
self.add_to_query(
|
|
506
|
-
"CREATE (prop_%s)<-[rel_prop_%s:%s $rel_prop ]-(rl)" % (prop_name, prop_name, prop.rel.type),
|
|
507
|
-
)
|
|
508
|
-
self.return_labels.append(f"rel_prop_{prop_name}")
|
|
509
552
|
|
|
510
553
|
|
|
511
554
|
class RelationshipDeleteQuery(RelationshipQuery):
|
|
512
555
|
name = "relationship_delete"
|
|
513
556
|
type = QueryType.WRITE
|
|
557
|
+
insert_return = False
|
|
558
|
+
raise_error_if_empty = False
|
|
514
559
|
|
|
515
|
-
def __init__(self, **kwargs):
|
|
560
|
+
def __init__(self, source_branch: Branch, destination_branch: Branch, **kwargs) -> None:
|
|
561
|
+
self.source_branch = source_branch
|
|
562
|
+
self.destination_branch = destination_branch
|
|
516
563
|
super().__init__(**kwargs)
|
|
517
564
|
|
|
518
|
-
if inspect.isclass(self.rel):
|
|
519
|
-
raise TypeError(
|
|
565
|
+
if inspect.isclass(self.rel) and not self.rel_id:
|
|
566
|
+
raise TypeError(
|
|
567
|
+
"An instance of Relationship or a relationship ID must be provided to RelationshipDeleteQuery"
|
|
568
|
+
)
|
|
520
569
|
|
|
521
570
|
async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG002
|
|
522
571
|
rel_filter, rel_params = self.branch.get_query_filter_path(at=self.at, variable_name="edge")
|
|
523
|
-
self.params["rel_id"] = self.rel.id
|
|
572
|
+
self.params["rel_id"] = self.rel_id or self.rel.id
|
|
524
573
|
self.params["branch"] = self.branch.name
|
|
525
|
-
self.params["rel_prop"] = self.get_relationship_properties_dict(
|
|
574
|
+
self.params["rel_prop"] = self.get_relationship_properties_dict(
|
|
575
|
+
status=RelationshipStatus.DELETED, user_id=self.user_id
|
|
576
|
+
)
|
|
577
|
+
self.params["user_id"] = self.user_id
|
|
526
578
|
self.params["at"] = self.at.to_string()
|
|
527
579
|
self.params.update(rel_params)
|
|
528
580
|
|
|
529
581
|
arrows = self.schema.get_query_arrows()
|
|
530
|
-
r1 = f"{arrows.left.start}[r1:
|
|
531
|
-
r2 = f"{arrows.right.start}[r2:
|
|
582
|
+
r1 = f"{arrows.left.start}[r1:IS_RELATED $rel_prop ]{arrows.left.end}"
|
|
583
|
+
r2 = f"{arrows.right.start}[r2:IS_RELATED $rel_prop ]{arrows.right.end}"
|
|
532
584
|
|
|
533
|
-
self.add_source_match_to_query(source_branch=self.
|
|
585
|
+
self.add_source_match_to_query(source_branch=self.source_branch)
|
|
534
586
|
self.add_dest_match_to_query(
|
|
535
|
-
destination_branch=self.
|
|
587
|
+
destination_branch=self.destination_branch,
|
|
536
588
|
destination_id=self.destination_id or self.destination.get_id(),
|
|
537
589
|
)
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
590
|
+
|
|
591
|
+
# if the IS_RELATED edges are already deleted on this branch, then we assume the delete already succeeded
|
|
592
|
+
rel_match_query = """
|
|
593
|
+
MATCH (s)%(arrows_l1)s[r1:IS_RELATED]%(arrows_l2)s(rl:Relationship { uuid: $rel_id })%(arrows_r1)s[r2:IS_RELATED]%(arrows_r2)s(d)
|
|
594
|
+
WHERE all(edge in [r1, r2] WHERE %(rel_filter)s)
|
|
595
|
+
ORDER BY r1.branch_level DESC, r1.from DESC, r1.status ASC, r2.branch_level DESC, r2.from DESC, r2.status ASC
|
|
541
596
|
LIMIT 1
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
LIMIT 1
|
|
551
|
-
CREATE (rl)-[deleted_edge:IS_VISIBLE $rel_prop]->(visible)
|
|
552
|
-
WITH edge
|
|
553
|
-
WHERE edge.branch = $branch
|
|
554
|
-
SET edge.to = $at
|
|
597
|
+
WITH s, rl, d, r1 AS source_edge, r2 AS destination_edge
|
|
598
|
+
WHERE source_edge.status = "active" OR destination_edge.status = "active"
|
|
599
|
+
""" % {
|
|
600
|
+
"rel_filter": rel_filter,
|
|
601
|
+
"arrows_l1": arrows.left.start,
|
|
602
|
+
"arrows_l2": arrows.left.end,
|
|
603
|
+
"arrows_r1": arrows.right.start,
|
|
604
|
+
"arrows_r2": arrows.right.end,
|
|
555
605
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
606
|
+
if self.branch.is_default or self.branch.is_global:
|
|
607
|
+
rel_match_query += """
|
|
608
|
+
SET rl.updated_at = $at, rl.updated_by = $user_id
|
|
609
|
+
SET s.updated_at = $at, s.updated_by = $user_id
|
|
610
|
+
SET d.updated_at = $at, d.updated_by = $user_id
|
|
611
|
+
"""
|
|
612
|
+
self.add_to_query(rel_match_query)
|
|
613
|
+
|
|
614
|
+
query = """
|
|
615
|
+
WITH s, rl, d, source_edge, destination_edge
|
|
616
|
+
// --------------
|
|
617
|
+
// create the deleted edges if the existing edges are on global or default branch
|
|
618
|
+
// --------------
|
|
619
|
+
CALL (s, source_edge, rl) {
|
|
620
|
+
WITH source_edge
|
|
621
|
+
WHERE source_edge.branch <> $branch
|
|
622
|
+
CREATE (s)%(r1)s(rl)
|
|
566
623
|
}
|
|
567
|
-
CALL (rl) {
|
|
568
|
-
|
|
569
|
-
WHERE
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
624
|
+
CALL (rl, destination_edge, d) {
|
|
625
|
+
WITH destination_edge
|
|
626
|
+
WHERE destination_edge.branch <> $branch
|
|
627
|
+
CREATE (rl)%(r2)s(d)
|
|
628
|
+
}
|
|
629
|
+
// --------------
|
|
630
|
+
// set the to time on the existing edges if they are on the current branch
|
|
631
|
+
// --------------
|
|
632
|
+
CALL (s, source_edge, rl) {
|
|
633
|
+
WITH source_edge
|
|
634
|
+
WHERE source_edge.branch = $branch
|
|
635
|
+
AND source_edge.to IS NULL
|
|
636
|
+
SET source_edge.to = $at, source_edge.to_user_id = $user_id
|
|
637
|
+
}
|
|
638
|
+
CALL (rl, destination_edge, d) {
|
|
639
|
+
WITH destination_edge
|
|
640
|
+
WHERE destination_edge.branch = $branch
|
|
641
|
+
AND destination_edge.to IS NULL
|
|
642
|
+
SET destination_edge.to = $at, destination_edge.to_user_id = $user_id
|
|
643
|
+
}
|
|
644
|
+
WITH rl
|
|
645
|
+
|
|
646
|
+
OPTIONAL MATCH (rl)-[edge:IS_PROTECTED|HAS_OWNER|HAS_SOURCE]->(peer)
|
|
647
|
+
WHERE %(rel_filter)s
|
|
648
|
+
ORDER BY type(edge), edge.branch_level DESC, edge.from DESC, edge.status ASC
|
|
649
|
+
WITH rl, type(edge) AS edge_type, head(collect(edge)) AS edge, head(collect(peer)) AS peer
|
|
650
|
+
|
|
651
|
+
CALL (rl, edge, edge_type, peer) {
|
|
574
652
|
WITH edge
|
|
575
|
-
WHERE edge.branch
|
|
576
|
-
|
|
653
|
+
WHERE edge.branch <> $branch
|
|
654
|
+
AND edge.status = "active"
|
|
655
|
+
CREATE (rl)-[deleted_edge:$(edge_type) $rel_prop]->(peer)
|
|
577
656
|
}
|
|
578
|
-
CALL (
|
|
579
|
-
MATCH (rl)-[edge:HAS_SOURCE]->(source_node)
|
|
580
|
-
WHERE %(rel_filter)s AND edge.status = "active"
|
|
581
|
-
WITH rl, edge, source_node
|
|
582
|
-
ORDER BY edge.branch_level DESC
|
|
583
|
-
LIMIT 1
|
|
584
|
-
CREATE (rl)-[deleted_edge:HAS_SOURCE $rel_prop]->(source_node)
|
|
657
|
+
CALL (edge) {
|
|
585
658
|
WITH edge
|
|
586
659
|
WHERE edge.branch = $branch
|
|
587
|
-
|
|
660
|
+
AND edge.status = "active"
|
|
661
|
+
AND edge.to IS NULL
|
|
662
|
+
SET edge.to = $at, edge.to_user_id = $user_id
|
|
663
|
+
}
|
|
664
|
+
""" % {
|
|
665
|
+
"r1": r1,
|
|
666
|
+
"r2": r2,
|
|
667
|
+
"rel_filter": rel_filter,
|
|
588
668
|
}
|
|
589
|
-
""" % {"r1": r1, "r2": r2, "rel_filter": rel_filter}
|
|
590
669
|
|
|
591
670
|
self.params["at"] = self.at.to_string()
|
|
592
671
|
self.return_labels = ["rl"]
|
|
@@ -609,8 +688,9 @@ class RelationshipGetPeerQuery(Query):
|
|
|
609
688
|
schema: RelationshipSchema | None = None,
|
|
610
689
|
branch: Branch | None = None,
|
|
611
690
|
at: Timestamp | str | None = None,
|
|
691
|
+
include_metadata: MetadataOptions = MetadataOptions.NONE,
|
|
612
692
|
**kwargs,
|
|
613
|
-
):
|
|
693
|
+
) -> None:
|
|
614
694
|
if not source and not source_ids:
|
|
615
695
|
raise ValueError("Either source or source_ids must be provided.")
|
|
616
696
|
if not rel and not rel_type:
|
|
@@ -631,6 +711,7 @@ class RelationshipGetPeerQuery(Query):
|
|
|
631
711
|
self.rel = rel
|
|
632
712
|
self.rel_type = rel_type or self.rel.rel_type
|
|
633
713
|
self.schema = schema or self.rel.schema
|
|
714
|
+
self.include_metadata = include_metadata
|
|
634
715
|
|
|
635
716
|
if not branch and inspect.isclass(rel) and not hasattr(rel, "branch"):
|
|
636
717
|
raise ValueError("Either an instance of Relationship or a valid branch must be provided.")
|
|
@@ -644,6 +725,124 @@ class RelationshipGetPeerQuery(Query):
|
|
|
644
725
|
|
|
645
726
|
super().__init__(**kwargs)
|
|
646
727
|
|
|
728
|
+
def _add_is_protected_query(self, branch_filter: str) -> None:
|
|
729
|
+
if not (self.include_metadata & MetadataOptions.IS_PROTECTED):
|
|
730
|
+
return
|
|
731
|
+
query = """
|
|
732
|
+
CALL (rl) {
|
|
733
|
+
MATCH (rl)-[r:IS_PROTECTED]-(is_protected)
|
|
734
|
+
WHERE %(branch_filter)s
|
|
735
|
+
RETURN r AS rel_is_protected, is_protected
|
|
736
|
+
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
737
|
+
LIMIT 1
|
|
738
|
+
}
|
|
739
|
+
""" % {"branch_filter": branch_filter}
|
|
740
|
+
self.add_to_query(query)
|
|
741
|
+
self.update_return_labels(["rel_is_protected", "is_protected"])
|
|
742
|
+
|
|
743
|
+
def _add_node_property_query(self, node_prop: str, branch_filter: str) -> None:
|
|
744
|
+
query = """
|
|
745
|
+
CALL (rl) {
|
|
746
|
+
OPTIONAL MATCH (rl)-[r:HAS_%(node_prop_type)s]-(%(node_prop)s)
|
|
747
|
+
WHERE %(branch_filter)s
|
|
748
|
+
WITH r, %(node_prop)s
|
|
749
|
+
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
750
|
+
LIMIT 1
|
|
751
|
+
RETURN CASE
|
|
752
|
+
WHEN r.status = "active" THEN %(node_prop)s
|
|
753
|
+
ELSE NULL
|
|
754
|
+
END AS %(node_prop)s,
|
|
755
|
+
CASE
|
|
756
|
+
WHEN r.status = "active" THEN r
|
|
757
|
+
ELSE NULL
|
|
758
|
+
END AS rel_%(node_prop)s
|
|
759
|
+
}
|
|
760
|
+
""" % {
|
|
761
|
+
"node_prop": node_prop,
|
|
762
|
+
"node_prop_type": node_prop.upper(),
|
|
763
|
+
"branch_filter": branch_filter,
|
|
764
|
+
}
|
|
765
|
+
self.add_to_query(query)
|
|
766
|
+
self.update_return_labels([f"rel_{node_prop}", node_prop])
|
|
767
|
+
|
|
768
|
+
def _add_has_owner_query(self, branch_filter: str) -> None:
|
|
769
|
+
if not (self.include_metadata & MetadataOptions.OWNER):
|
|
770
|
+
return
|
|
771
|
+
self._add_node_property_query(node_prop="owner", branch_filter=branch_filter)
|
|
772
|
+
|
|
773
|
+
def _add_has_source_query(self, branch_filter: str) -> None:
|
|
774
|
+
if not (self.include_metadata & MetadataOptions.SOURCE):
|
|
775
|
+
return
|
|
776
|
+
self._add_node_property_query(node_prop="source", branch_filter=branch_filter)
|
|
777
|
+
|
|
778
|
+
def _add_created_metadata_to_query(self) -> None:
|
|
779
|
+
if not (self.include_metadata & (MetadataOptions.CREATED_AT | MetadataOptions.CREATED_BY)):
|
|
780
|
+
return
|
|
781
|
+
if self.branch.is_default or self.branch.is_global:
|
|
782
|
+
last_created_query = """
|
|
783
|
+
WITH *, rl.created_at AS created_at, rl.created_by AS created_by
|
|
784
|
+
"""
|
|
785
|
+
else:
|
|
786
|
+
last_created_query = """
|
|
787
|
+
CALL (rels) {
|
|
788
|
+
UNWIND rels AS rel
|
|
789
|
+
RETURN rel.from AS created_at, rel.from_user_id AS created_by
|
|
790
|
+
ORDER BY created_at ASC
|
|
791
|
+
LIMIT 1
|
|
792
|
+
}
|
|
793
|
+
"""
|
|
794
|
+
self.add_to_query(last_created_query)
|
|
795
|
+
self.update_return_labels(["created_at", "created_by"])
|
|
796
|
+
|
|
797
|
+
def _add_updated_metadata_to_query(self, branch_filter_str: str) -> None:
|
|
798
|
+
if not (self.include_metadata & (MetadataOptions.UPDATED_AT | MetadataOptions.UPDATED_BY)):
|
|
799
|
+
return
|
|
800
|
+
self.update_return_labels(["updated_at", "updated_by"])
|
|
801
|
+
if self.branch.is_default or self.branch.is_global:
|
|
802
|
+
last_updated_query = """
|
|
803
|
+
WITH *, rl.updated_at AS updated_at, rl.updated_by AS updated_by
|
|
804
|
+
"""
|
|
805
|
+
self.add_to_query(last_updated_query)
|
|
806
|
+
return
|
|
807
|
+
|
|
808
|
+
# query for non-default, non-global branches
|
|
809
|
+
if self.branch_agnostic:
|
|
810
|
+
time_details = """
|
|
811
|
+
WITH [r.from, r.from_user_id] AS from_details, [r.to, r.to_user_id] AS to_details
|
|
812
|
+
"""
|
|
813
|
+
else:
|
|
814
|
+
time_details = """
|
|
815
|
+
WITH CASE
|
|
816
|
+
WHEN $is_branch_agnostic THEN [r.from, r.from_user_id]
|
|
817
|
+
WHEN r.branch IN $branch0 AND r.from < $time0 THEN [r.from, r.from_user_id]
|
|
818
|
+
WHEN r.branch IN $branch1 AND r.from < $time1 THEN [r.from, r.from_user_id]
|
|
819
|
+
ELSE [NULL, NULL]
|
|
820
|
+
END AS from_details,
|
|
821
|
+
CASE
|
|
822
|
+
WHEN $is_branch_agnostic THEN [r.to, r.to_user_id]
|
|
823
|
+
WHEN r.branch IN $branch0 AND r.to < $time0 THEN [r.to, r.to_user_id]
|
|
824
|
+
WHEN r.branch IN $branch1 AND r.to < $time1 THEN [r.to, r.to_user_id]
|
|
825
|
+
ELSE [NULL, NULL]
|
|
826
|
+
END AS to_details
|
|
827
|
+
"""
|
|
828
|
+
last_updated_query = """
|
|
829
|
+
CALL (rl) {
|
|
830
|
+
MATCH (rl)-[r]-(property)
|
|
831
|
+
WHERE %(branch_filter)s
|
|
832
|
+
%(time_details)s
|
|
833
|
+
WITH collect(from_details) AS from_details_list, collect(to_details) AS to_details_list
|
|
834
|
+
WITH from_details_list + to_details_list AS details_list
|
|
835
|
+
UNWIND details_list AS one_details
|
|
836
|
+
WITH one_details[0] AS updated_at, one_details[1] AS updated_by
|
|
837
|
+
WHERE updated_at IS NOT NULL
|
|
838
|
+
WITH updated_at, updated_by
|
|
839
|
+
ORDER BY updated_at DESC
|
|
840
|
+
LIMIT 1
|
|
841
|
+
RETURN updated_at, updated_by
|
|
842
|
+
}
|
|
843
|
+
""" % {"branch_filter": branch_filter_str, "time_details": time_details}
|
|
844
|
+
self.add_to_query(last_updated_query)
|
|
845
|
+
|
|
647
846
|
async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG002
|
|
648
847
|
branch_filter, branch_params = self.branch.get_query_filter_path(
|
|
649
848
|
at=self.at, branch_agnostic=self.branch_agnostic
|
|
@@ -653,6 +852,7 @@ class RelationshipGetPeerQuery(Query):
|
|
|
653
852
|
|
|
654
853
|
peer_schema = self.schema.get_peer_schema(db=db, branch=self.branch)
|
|
655
854
|
|
|
855
|
+
self.params["is_branch_agnostic"] = self.branch_agnostic
|
|
656
856
|
self.params["source_ids"] = self.source_ids
|
|
657
857
|
self.params["rel_identifier"] = self.schema.identifier
|
|
658
858
|
self.params["peer_kind"] = self.schema.peer
|
|
@@ -738,47 +938,13 @@ class RelationshipGetPeerQuery(Query):
|
|
|
738
938
|
)
|
|
739
939
|
self.add_subquery(subquery=subquery, node_alias="peer", with_clause=with_str)
|
|
740
940
|
# ----------------------------------------------------------------------------
|
|
741
|
-
#
|
|
941
|
+
# add metadata
|
|
742
942
|
# ----------------------------------------------------------------------------
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
749
|
-
LIMIT 1
|
|
750
|
-
}
|
|
751
|
-
CALL (rl) {
|
|
752
|
-
MATCH (rl)-[r:IS_PROTECTED]-(is_protected)
|
|
753
|
-
WHERE %(branch_filter)s
|
|
754
|
-
RETURN r AS rel_is_protected, is_protected
|
|
755
|
-
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
756
|
-
LIMIT 1
|
|
757
|
-
}
|
|
758
|
-
""" % {"branch_filter": branch_filter}
|
|
759
|
-
|
|
760
|
-
self.add_to_query(query)
|
|
761
|
-
|
|
762
|
-
self.update_return_labels(["rel_is_visible", "rel_is_protected", "is_visible", "is_protected"])
|
|
763
|
-
|
|
764
|
-
# Add Node Properties
|
|
765
|
-
# We must query them one by one otherwise the second one won't return
|
|
766
|
-
for node_prop in ["source", "owner"]:
|
|
767
|
-
query = """
|
|
768
|
-
CALL (rl) {
|
|
769
|
-
OPTIONAL MATCH (rl)-[r:HAS_%(node_prop_type)s]-(%(node_prop)s)
|
|
770
|
-
WHERE %(branch_filter)s
|
|
771
|
-
RETURN r AS rel_%(node_prop)s, %(node_prop)s
|
|
772
|
-
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
|
|
773
|
-
LIMIT 1
|
|
774
|
-
}
|
|
775
|
-
""" % {
|
|
776
|
-
"node_prop": node_prop,
|
|
777
|
-
"node_prop_type": node_prop.upper(),
|
|
778
|
-
"branch_filter": branch_filter,
|
|
779
|
-
}
|
|
780
|
-
self.add_to_query(query)
|
|
781
|
-
self.update_return_labels([f"rel_{node_prop}", node_prop])
|
|
943
|
+
self._add_is_protected_query(branch_filter)
|
|
944
|
+
self._add_has_owner_query(branch_filter)
|
|
945
|
+
self._add_has_source_query(branch_filter)
|
|
946
|
+
self._add_created_metadata_to_query()
|
|
947
|
+
self._add_updated_metadata_to_query(branch_filter_str=branch_filter)
|
|
782
948
|
|
|
783
949
|
self.add_to_query("WITH " + ",".join(self.return_labels))
|
|
784
950
|
|
|
@@ -823,6 +989,17 @@ class RelationshipGetPeerQuery(Query):
|
|
|
823
989
|
rels = result.get("rels")
|
|
824
990
|
source_node = result.get_node("source_node")
|
|
825
991
|
peer_node = result.get_node("peer")
|
|
992
|
+
|
|
993
|
+
if self.include_metadata & MetadataOptions.CREATED_AT:
|
|
994
|
+
created_at_str = result.get("created_at")
|
|
995
|
+
created_at = Timestamp(created_at_str) if created_at_str else None
|
|
996
|
+
else:
|
|
997
|
+
created_at = None
|
|
998
|
+
if self.include_metadata & MetadataOptions.UPDATED_AT:
|
|
999
|
+
updated_at_str = result.get("updated_at")
|
|
1000
|
+
updated_at = Timestamp(updated_at_str) if updated_at_str else None
|
|
1001
|
+
else:
|
|
1002
|
+
updated_at = None
|
|
826
1003
|
data = RelationshipPeerData(
|
|
827
1004
|
source_id=source_node.get("uuid"),
|
|
828
1005
|
source_db_id=source_node.element_id,
|
|
@@ -832,88 +1009,44 @@ class RelationshipGetPeerQuery(Query):
|
|
|
832
1009
|
peer_kind=peer_node.get("kind"),
|
|
833
1010
|
rel_node_db_id=result.get("rl").element_id,
|
|
834
1011
|
rel_node_id=result.get("rl").get("uuid"),
|
|
835
|
-
updated_at=rels[0]["from"],
|
|
836
1012
|
rels=[RelData.from_db(rel) for rel in rels],
|
|
837
1013
|
branch=self.branch.name,
|
|
1014
|
+
created_at=created_at,
|
|
1015
|
+
created_by=result.get("created_by") if self.include_metadata & MetadataOptions.CREATED_BY else None,
|
|
1016
|
+
updated_at=updated_at,
|
|
1017
|
+
updated_by=result.get("updated_by") if self.include_metadata & MetadataOptions.UPDATED_BY else None,
|
|
838
1018
|
properties={},
|
|
839
1019
|
)
|
|
840
1020
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
)
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
class RelationshipGetQuery(RelationshipQuery):
|
|
865
|
-
name = "relationship_get"
|
|
866
|
-
|
|
867
|
-
type: QueryType = QueryType.READ
|
|
868
|
-
|
|
869
|
-
async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG002
|
|
870
|
-
self.params["name"] = self.schema.identifier
|
|
871
|
-
self.params["branch"] = self.branch.name
|
|
872
|
-
|
|
873
|
-
rels_filter, rels_params = self.branch.get_query_filter_relationships(
|
|
874
|
-
rel_labels=["r1", "r2"], at=self.at.to_string(), include_outside_parentheses=True
|
|
875
|
-
)
|
|
876
|
-
|
|
877
|
-
self.params.update(rels_params)
|
|
878
|
-
|
|
879
|
-
arrows = self.schema.get_query_arrows()
|
|
880
|
-
r1 = f"{arrows.left.start}[r1:{self.rel.rel_type}]{arrows.left.end}"
|
|
881
|
-
r2 = f"{arrows.right.start}[r2:{self.rel.rel_type}]{arrows.right.end}"
|
|
882
|
-
|
|
883
|
-
self.add_source_match_to_query(source_branch=self.source.get_branch_based_on_support_type())
|
|
884
|
-
self.add_dest_match_to_query(
|
|
885
|
-
destination_branch=self.destination.get_branch_based_on_support_type(),
|
|
886
|
-
destination_id=self.destination_id or self.destination.get_id(),
|
|
887
|
-
)
|
|
888
|
-
query = """
|
|
889
|
-
MATCH (s)%s(rl:Relationship { name: $name })%s(d)
|
|
890
|
-
WHERE %s
|
|
891
|
-
ORDER BY r1.branch_level DESC, r1.from DESC, r1.status ASC, r2.branch_level DESC, r2.from DESC, r2.status ASC
|
|
892
|
-
WITH *, r1.status = "active" AND r2.status = "active" AS is_active
|
|
893
|
-
LIMIT 1
|
|
894
|
-
""" % (
|
|
895
|
-
r1,
|
|
896
|
-
r2,
|
|
897
|
-
"\n AND ".join(rels_filter),
|
|
898
|
-
)
|
|
899
|
-
|
|
900
|
-
self.params["at"] = self.at.to_string()
|
|
901
|
-
|
|
902
|
-
self.add_to_query(query)
|
|
903
|
-
self.return_labels = ["s", "d", "rl", "r2", "is_active"]
|
|
904
|
-
|
|
905
|
-
def is_already_deleted(self) -> bool:
|
|
906
|
-
result = self.get_result()
|
|
907
|
-
if not result:
|
|
908
|
-
return False
|
|
909
|
-
return result.get("is_active") is False
|
|
1021
|
+
prop, metadata_option = ("is_protected", MetadataOptions.IS_PROTECTED)
|
|
1022
|
+
if self.include_metadata & metadata_option:
|
|
1023
|
+
prop_node = result.get(prop)
|
|
1024
|
+
if prop_node:
|
|
1025
|
+
data.properties[prop] = FlagPropertyData(
|
|
1026
|
+
name=prop,
|
|
1027
|
+
prop_db_id=prop_node.element_id,
|
|
1028
|
+
rel=RelData.from_db(result.get(f"rel_{prop}")),
|
|
1029
|
+
value=prop_node.get("value"),
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1032
|
+
for prop, metadata_option in [("owner", MetadataOptions.OWNER), ("source", MetadataOptions.SOURCE)]:
|
|
1033
|
+
if not self.include_metadata & metadata_option:
|
|
1034
|
+
continue
|
|
1035
|
+
prop_node = result.get(prop)
|
|
1036
|
+
if not prop_node:
|
|
1037
|
+
continue
|
|
1038
|
+
data.properties[prop] = NodePropertyData(
|
|
1039
|
+
name=prop,
|
|
1040
|
+
prop_db_id=prop_node.element_id,
|
|
1041
|
+
rel=RelData.from_db(result.get(f"rel_{prop}")),
|
|
1042
|
+
value=prop_node.get("uuid"),
|
|
1043
|
+
)
|
|
910
1044
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
return None
|
|
1045
|
+
if prop == "source" and InfrahubKind.PROFILE in prop_node.labels:
|
|
1046
|
+
data.is_from_profile = True
|
|
1047
|
+
data.profile_id = prop_node._properties["uuid"]
|
|
915
1048
|
|
|
916
|
-
|
|
1049
|
+
yield data
|
|
917
1050
|
|
|
918
1051
|
|
|
919
1052
|
class RelationshipGetByIdentifierQuery(Query):
|
|
@@ -954,7 +1087,7 @@ class RelationshipGetByIdentifierQuery(Query):
|
|
|
954
1087
|
self.params["at"] = self.at.to_string()
|
|
955
1088
|
|
|
956
1089
|
rels_filter, rels_params = self.branch.get_query_filter_relationships(
|
|
957
|
-
rel_labels=["r1", "r2"], at=self.at
|
|
1090
|
+
rel_labels=["r1", "r2"], at=self.at, include_outside_parentheses=True
|
|
958
1091
|
)
|
|
959
1092
|
self.params.update(rels_params)
|
|
960
1093
|
|
|
@@ -991,6 +1124,25 @@ class RelationshipGetByIdentifierQuery(Query):
|
|
|
991
1124
|
yield data
|
|
992
1125
|
|
|
993
1126
|
|
|
1127
|
+
@dataclass(frozen=True)
|
|
1128
|
+
class RelationshipCountPerNodeResult:
|
|
1129
|
+
"""Result from RelationshipCountPerNodeQuery containing peer count info."""
|
|
1130
|
+
|
|
1131
|
+
peer_uuid: str
|
|
1132
|
+
"""UUID of the peer node."""
|
|
1133
|
+
|
|
1134
|
+
count: int
|
|
1135
|
+
"""Number of relationship peers for this node."""
|
|
1136
|
+
|
|
1137
|
+
@classmethod
|
|
1138
|
+
def from_db(cls, result: QueryResult) -> RelationshipCountPerNodeResult:
|
|
1139
|
+
"""Convert raw QueryResult to typed dataclass."""
|
|
1140
|
+
return cls(
|
|
1141
|
+
peer_uuid=result.get_as_type("peer_node.uuid", str),
|
|
1142
|
+
count=result.get_as_type("nbr_peers", int),
|
|
1143
|
+
)
|
|
1144
|
+
|
|
1145
|
+
|
|
994
1146
|
class RelationshipCountPerNodeQuery(Query):
|
|
995
1147
|
name = "relationship_count_per_node"
|
|
996
1148
|
type: QueryType = QueryType.READ
|
|
@@ -1001,7 +1153,7 @@ class RelationshipCountPerNodeQuery(Query):
|
|
|
1001
1153
|
identifier: str,
|
|
1002
1154
|
direction: RelationshipDirection,
|
|
1003
1155
|
**kwargs,
|
|
1004
|
-
):
|
|
1156
|
+
) -> None:
|
|
1005
1157
|
self.node_ids = node_ids
|
|
1006
1158
|
self.identifier = identifier
|
|
1007
1159
|
self.direction = direction
|
|
@@ -1039,10 +1191,18 @@ class RelationshipCountPerNodeQuery(Query):
|
|
|
1039
1191
|
self.order_by = ["peer_node.uuid"]
|
|
1040
1192
|
self.return_labels = ["peer_node.uuid", "COUNT(peer_node.uuid) as nbr_peers"]
|
|
1041
1193
|
|
|
1194
|
+
def get_data(self) -> list[RelationshipCountPerNodeResult]:
|
|
1195
|
+
"""Return results as typed dataclass instances.
|
|
1196
|
+
|
|
1197
|
+
Returns:
|
|
1198
|
+
List of RelationshipCountPerNodeResult containing peer count info.
|
|
1199
|
+
"""
|
|
1200
|
+
return [RelationshipCountPerNodeResult.from_db(result) for result in self.get_results()]
|
|
1201
|
+
|
|
1042
1202
|
async def get_count_per_peer(self) -> dict[str, int]:
|
|
1043
1203
|
data: dict[str, int] = {}
|
|
1044
|
-
for
|
|
1045
|
-
data[
|
|
1204
|
+
for item in self.get_data():
|
|
1205
|
+
data[item.peer_uuid] = item.count
|
|
1046
1206
|
|
|
1047
1207
|
for node_id in self.node_ids:
|
|
1048
1208
|
if node_id not in data:
|
|
@@ -1051,6 +1211,33 @@ class RelationshipCountPerNodeQuery(Query):
|
|
|
1051
1211
|
return data
|
|
1052
1212
|
|
|
1053
1213
|
|
|
1214
|
+
@dataclass(frozen=True)
|
|
1215
|
+
class RelationshipDeleteAllQueryResult:
|
|
1216
|
+
"""Result from RelationshipDeleteAllQuery containing deleted relationship info."""
|
|
1217
|
+
|
|
1218
|
+
uuid: str
|
|
1219
|
+
"""UUID of the peer node whose relationship was deleted."""
|
|
1220
|
+
|
|
1221
|
+
kind: str
|
|
1222
|
+
"""Kind/type of the peer node."""
|
|
1223
|
+
|
|
1224
|
+
rel_identifier: str
|
|
1225
|
+
"""Relationship schema identifier name."""
|
|
1226
|
+
|
|
1227
|
+
rel_direction: str
|
|
1228
|
+
"""Direction of the relationship ("outbound" or "inbound")."""
|
|
1229
|
+
|
|
1230
|
+
@classmethod
|
|
1231
|
+
def from_db(cls, result: QueryResult) -> RelationshipDeleteAllQueryResult:
|
|
1232
|
+
"""Convert raw QueryResult to typed dataclass."""
|
|
1233
|
+
return cls(
|
|
1234
|
+
uuid=result.get_as_type("uuid", str),
|
|
1235
|
+
kind=result.get_as_type("kind", str),
|
|
1236
|
+
rel_identifier=result.get_as_type("rel_identifier", str),
|
|
1237
|
+
rel_direction=result.get_as_type("rel_direction", str),
|
|
1238
|
+
)
|
|
1239
|
+
|
|
1240
|
+
|
|
1054
1241
|
class RelationshipDeleteAllQuery(Query):
|
|
1055
1242
|
"""
|
|
1056
1243
|
Delete all relationships linked to a given node on a given branch at a given time. For every IS_RELATED edge:
|
|
@@ -1065,19 +1252,20 @@ class RelationshipDeleteAllQuery(Query):
|
|
|
1065
1252
|
type = QueryType.WRITE
|
|
1066
1253
|
insert_return = False
|
|
1067
1254
|
|
|
1068
|
-
def __init__(self, node_id: str, **kwargs):
|
|
1255
|
+
def __init__(self, node_id: str, **kwargs) -> None:
|
|
1069
1256
|
self.node_id = node_id
|
|
1070
1257
|
super().__init__(**kwargs)
|
|
1071
1258
|
|
|
1072
1259
|
async def query_init(self, db: InfrahubDatabase, **kwargs) -> None:
|
|
1073
1260
|
self.params["source_id"] = kwargs["node_id"]
|
|
1074
1261
|
self.params["branch"] = self.branch.name
|
|
1075
|
-
|
|
1262
|
+
self.params["user_id"] = self.user_id
|
|
1076
1263
|
self.params["rel_prop"] = {
|
|
1077
1264
|
"branch": self.branch.name,
|
|
1078
1265
|
"branch_level": self.branch.hierarchy_level,
|
|
1079
1266
|
"status": RelationshipStatus.DELETED.value,
|
|
1080
1267
|
"from": self.at.to_string(),
|
|
1268
|
+
"from_user_id": self.user_id,
|
|
1081
1269
|
}
|
|
1082
1270
|
|
|
1083
1271
|
self.params["at"] = self.at.to_string()
|
|
@@ -1087,14 +1275,20 @@ class RelationshipDeleteAllQuery(Query):
|
|
|
1087
1275
|
)
|
|
1088
1276
|
self.params.update(rel_params)
|
|
1089
1277
|
|
|
1090
|
-
|
|
1278
|
+
rel_match_query = """
|
|
1091
1279
|
MATCH (s:Node { uuid: $source_id })-[active_edge:IS_RELATED]-(rl:Relationship)
|
|
1092
1280
|
WHERE %(active_rel_filter)s AND active_edge.status = "active"
|
|
1093
|
-
WITH DISTINCT rl
|
|
1094
1281
|
""" % {"active_rel_filter": active_rel_filter}
|
|
1282
|
+
if self.branch.is_default or self.branch.is_global:
|
|
1283
|
+
rel_match_query += """
|
|
1284
|
+
SET rl.updated_at = $at, rl.updated_by = $user_id
|
|
1285
|
+
"""
|
|
1286
|
+
rel_match_query += """
|
|
1287
|
+
WITH DISTINCT rl
|
|
1288
|
+
"""
|
|
1289
|
+
self.add_to_query(rel_match_query)
|
|
1095
1290
|
|
|
1096
1291
|
edge_types = [
|
|
1097
|
-
DatabaseEdgeType.IS_VISIBLE.value,
|
|
1098
1292
|
DatabaseEdgeType.IS_PROTECTED.value,
|
|
1099
1293
|
DatabaseEdgeType.HAS_OWNER.value,
|
|
1100
1294
|
DatabaseEdgeType.HAS_SOURCE.value,
|
|
@@ -1110,7 +1304,7 @@ class RelationshipDeleteAllQuery(Query):
|
|
|
1110
1304
|
SET deleted_edge.hierarchy = active_edge.hierarchy
|
|
1111
1305
|
WITH active_edge, n
|
|
1112
1306
|
WHERE active_edge.branch = $branch AND active_edge.to IS NULL
|
|
1113
|
-
SET active_edge.to = $at
|
|
1307
|
+
SET active_edge.to = $at, active_edge.to_user_id = $user_id
|
|
1114
1308
|
}
|
|
1115
1309
|
""" % {
|
|
1116
1310
|
"arrow_left": arrow_left,
|
|
@@ -1119,10 +1313,14 @@ class RelationshipDeleteAllQuery(Query):
|
|
|
1119
1313
|
"edge_type": edge_type,
|
|
1120
1314
|
}
|
|
1121
1315
|
|
|
1122
|
-
|
|
1316
|
+
self.add_to_query(sub_query)
|
|
1123
1317
|
|
|
1124
1318
|
# We only want to return uuid/kind of `Node` connected through `IS_RELATED` edges.
|
|
1125
|
-
|
|
1319
|
+
peer_node_metadata_update = ""
|
|
1320
|
+
if self.branch.is_default or self.branch.is_global:
|
|
1321
|
+
peer_node_metadata_update = "SET n.updated_at = $at, n.updated_by = $user_id"
|
|
1322
|
+
|
|
1323
|
+
query = """
|
|
1126
1324
|
CALL (rl) {
|
|
1127
1325
|
MATCH (rl)-[active_edge:IS_RELATED]->(n)
|
|
1128
1326
|
WHERE %(active_rel_filter)s
|
|
@@ -1132,9 +1330,10 @@ class RelationshipDeleteAllQuery(Query):
|
|
|
1132
1330
|
WHERE active_edge.status = "active"
|
|
1133
1331
|
CREATE (rl)-[deleted_edge:IS_RELATED $rel_prop]->(n)
|
|
1134
1332
|
SET deleted_edge.hierarchy = active_edge.hierarchy
|
|
1333
|
+
%(peer_node_metadata_update)s
|
|
1135
1334
|
WITH rl, active_edge, n
|
|
1136
1335
|
WHERE active_edge.branch = $branch AND active_edge.to IS NULL
|
|
1137
|
-
SET active_edge.to = $at
|
|
1336
|
+
SET active_edge.to = $at, active_edge.to_user_id = $user_id
|
|
1138
1337
|
RETURN
|
|
1139
1338
|
n.uuid as uuid,
|
|
1140
1339
|
n.kind as kind,
|
|
@@ -1151,9 +1350,10 @@ class RelationshipDeleteAllQuery(Query):
|
|
|
1151
1350
|
WHERE active_edge.status = "active"
|
|
1152
1351
|
CREATE (rl)<-[deleted_edge:IS_RELATED $rel_prop]-(n)
|
|
1153
1352
|
SET deleted_edge.hierarchy = active_edge.hierarchy
|
|
1353
|
+
%(peer_node_metadata_update)s
|
|
1154
1354
|
WITH rl, active_edge, n
|
|
1155
1355
|
WHERE active_edge.branch = $branch AND active_edge.to IS NULL
|
|
1156
|
-
SET active_edge.to = $at
|
|
1356
|
+
SET active_edge.to = $at, active_edge.to_user_id = $user_id
|
|
1157
1357
|
RETURN
|
|
1158
1358
|
n.uuid as uuid,
|
|
1159
1359
|
n.kind as kind,
|
|
@@ -1161,54 +1361,60 @@ class RelationshipDeleteAllQuery(Query):
|
|
|
1161
1361
|
"inbound" as rel_direction
|
|
1162
1362
|
}
|
|
1163
1363
|
RETURN DISTINCT uuid, kind, rel_identifier, rel_direction
|
|
1164
|
-
""" % {
|
|
1165
|
-
|
|
1364
|
+
""" % {
|
|
1365
|
+
"active_rel_filter": active_rel_filter,
|
|
1366
|
+
"id_func": db.get_id_function_name(),
|
|
1367
|
+
"peer_node_metadata_update": peer_node_metadata_update,
|
|
1368
|
+
}
|
|
1166
1369
|
self.add_to_query(query)
|
|
1370
|
+
self.return_labels = ["uuid", "kind", "rel_identifier", "rel_direction"]
|
|
1371
|
+
|
|
1372
|
+
def get_data(self) -> list[RelationshipDeleteAllQueryResult]:
|
|
1373
|
+
"""Return results as typed dataclass instances.
|
|
1374
|
+
|
|
1375
|
+
Returns:
|
|
1376
|
+
List of RelationshipDeleteAllQueryResult containing deleted relationship info.
|
|
1377
|
+
"""
|
|
1378
|
+
return [RelationshipDeleteAllQueryResult.from_db(result) for result in self.get_results()]
|
|
1167
1379
|
|
|
1168
1380
|
def get_deleted_relationships_changelog(
|
|
1169
1381
|
self, node_schema: NodeSchema
|
|
1170
1382
|
) -> list[RelationshipCardinalityOneChangelog | RelationshipCardinalityManyChangelog]:
|
|
1171
|
-
rel_identifier_to_changelog_mapper = {}
|
|
1383
|
+
rel_identifier_to_changelog_mapper: dict[str, ChangelogRelationshipMapper] = {}
|
|
1172
1384
|
|
|
1173
|
-
for
|
|
1174
|
-
|
|
1175
|
-
if peer_uuid == self.node_id:
|
|
1385
|
+
for item in self.get_data():
|
|
1386
|
+
if item.uuid == self.node_id:
|
|
1176
1387
|
continue
|
|
1177
1388
|
|
|
1178
|
-
rel_identifier = result.data["rel_identifier"]
|
|
1179
|
-
kind = result.data["kind"]
|
|
1180
1389
|
deleted_rel_schemas = [
|
|
1181
|
-
rel_schema for rel_schema in node_schema.relationships if rel_schema.identifier == rel_identifier
|
|
1390
|
+
rel_schema for rel_schema in node_schema.relationships if rel_schema.identifier == item.rel_identifier
|
|
1182
1391
|
]
|
|
1183
1392
|
|
|
1184
1393
|
if len(deleted_rel_schemas) == 0:
|
|
1185
1394
|
continue # TODO Unidirectional relationship changelog should be handled, cf IFC-1319.
|
|
1186
1395
|
|
|
1187
1396
|
if len(deleted_rel_schemas) > 2:
|
|
1188
|
-
log.error(f"Duplicated relationship schema with identifier {rel_identifier}")
|
|
1397
|
+
log.error(f"Duplicated relationship schema with identifier {item.rel_identifier}")
|
|
1189
1398
|
continue
|
|
1190
1399
|
|
|
1191
1400
|
if len(deleted_rel_schemas) == 2:
|
|
1192
1401
|
# Hierarchical schema nodes have 2 relationships with `parent_child` identifiers,
|
|
1193
1402
|
# which are differentiated by their direction within the database.
|
|
1194
|
-
# assert rel_identifier != PARENT_CHILD_IDENTIFIER
|
|
1195
|
-
|
|
1196
|
-
rel_direction = result.data["rel_direction"]
|
|
1197
1403
|
deleted_rel_schema = (
|
|
1198
1404
|
deleted_rel_schemas[0]
|
|
1199
|
-
if deleted_rel_schemas[0].direction.value == rel_direction
|
|
1405
|
+
if deleted_rel_schemas[0].direction.value == item.rel_direction
|
|
1200
1406
|
else deleted_rel_schemas[1]
|
|
1201
1407
|
)
|
|
1202
1408
|
else:
|
|
1203
1409
|
deleted_rel_schema = deleted_rel_schemas[0]
|
|
1204
1410
|
|
|
1205
1411
|
try:
|
|
1206
|
-
changelog_mapper = rel_identifier_to_changelog_mapper[rel_identifier]
|
|
1412
|
+
changelog_mapper = rel_identifier_to_changelog_mapper[item.rel_identifier]
|
|
1207
1413
|
except KeyError:
|
|
1208
1414
|
changelog_mapper = ChangelogRelationshipMapper(schema=deleted_rel_schema)
|
|
1209
|
-
rel_identifier_to_changelog_mapper[rel_identifier] = changelog_mapper
|
|
1415
|
+
rel_identifier_to_changelog_mapper[item.rel_identifier] = changelog_mapper
|
|
1210
1416
|
|
|
1211
|
-
changelog_mapper.delete_relationship(peer_id=
|
|
1417
|
+
changelog_mapper.delete_relationship(peer_id=item.uuid, peer_kind=item.kind, rel_schema=deleted_rel_schema)
|
|
1212
1418
|
|
|
1213
1419
|
return [changelog_mapper.changelog for changelog_mapper in rel_identifier_to_changelog_mapper.values()]
|
|
1214
1420
|
|
|
@@ -1222,7 +1428,7 @@ class GetAllPeersIds(Query):
|
|
|
1222
1428
|
type: QueryType = QueryType.READ
|
|
1223
1429
|
insert_return = False
|
|
1224
1430
|
|
|
1225
|
-
def __init__(self, node_id: str, exclude_identifiers: list[str], **kwargs):
|
|
1431
|
+
def __init__(self, node_id: str, exclude_identifiers: list[str], **kwargs) -> None:
|
|
1226
1432
|
self.node_id = node_id
|
|
1227
1433
|
self.exclude_identifiers = exclude_identifiers
|
|
1228
1434
|
super().__init__(**kwargs)
|