infrahub-server 1.2.7__py3-none-any.whl → 1.2.9__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/api/transformation.py +1 -0
- infrahub/artifacts/models.py +4 -0
- infrahub/cli/db.py +15 -6
- infrahub/computed_attribute/tasks.py +34 -12
- infrahub/config.py +2 -1
- infrahub/constants/__init__.py +0 -0
- infrahub/core/branch/tasks.py +0 -2
- infrahub/core/constants/__init__.py +1 -0
- infrahub/core/diff/calculator.py +4 -3
- infrahub/core/diff/combiner.py +1 -2
- infrahub/core/diff/coordinator.py +44 -28
- infrahub/core/diff/data_check_synchronizer.py +3 -2
- infrahub/core/diff/enricher/hierarchy.py +38 -27
- infrahub/core/diff/ipam_diff_parser.py +5 -4
- infrahub/core/diff/merger/merger.py +20 -18
- infrahub/core/diff/model/field_specifiers_map.py +64 -0
- infrahub/core/diff/model/path.py +55 -58
- infrahub/core/diff/parent_node_adder.py +14 -16
- infrahub/core/diff/query/drop_nodes.py +42 -0
- infrahub/core/diff/query/field_specifiers.py +8 -7
- infrahub/core/diff/query/filters.py +15 -1
- infrahub/core/diff/query/save.py +3 -0
- infrahub/core/diff/query_parser.py +49 -52
- infrahub/core/diff/repository/deserializer.py +36 -23
- infrahub/core/diff/repository/repository.py +31 -12
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/graph/index.py +3 -1
- infrahub/core/initialization.py +23 -7
- infrahub/core/manager.py +16 -5
- infrahub/core/migrations/graph/__init__.py +2 -0
- infrahub/core/migrations/graph/m014_remove_index_attr_value.py +9 -8
- infrahub/core/migrations/graph/m027_delete_isolated_nodes.py +50 -0
- infrahub/core/protocols.py +1 -0
- infrahub/core/query/branch.py +27 -17
- infrahub/core/query/diff.py +65 -38
- infrahub/core/query/node.py +111 -33
- infrahub/core/query/relationship.py +17 -3
- infrahub/core/query/subquery.py +2 -2
- infrahub/core/schema/definitions/core/builtin.py +2 -4
- infrahub/core/schema/definitions/core/transform.py +1 -0
- infrahub/core/schema/schema_branch.py +3 -0
- infrahub/core/validators/aggregated_checker.py +2 -2
- infrahub/core/validators/uniqueness/query.py +30 -9
- infrahub/database/__init__.py +1 -16
- infrahub/database/index.py +1 -1
- infrahub/database/memgraph.py +1 -12
- infrahub/database/neo4j.py +1 -13
- infrahub/git/integrator.py +27 -3
- infrahub/git/models.py +4 -0
- infrahub/git/tasks.py +3 -0
- infrahub/git_credential/helper.py +2 -2
- infrahub/graphql/mutations/computed_attribute.py +5 -1
- infrahub/graphql/queries/diff/tree.py +2 -1
- infrahub/message_bus/operations/requests/proposed_change.py +6 -0
- infrahub/message_bus/types.py +3 -0
- infrahub/patch/queries/consolidate_duplicated_nodes.py +109 -0
- infrahub/patch/queries/delete_duplicated_edges.py +138 -0
- infrahub/proposed_change/tasks.py +1 -0
- infrahub/server.py +1 -3
- infrahub/transformations/models.py +3 -0
- infrahub/transformations/tasks.py +1 -0
- infrahub/trigger/models.py +11 -1
- infrahub/trigger/setup.py +38 -13
- infrahub/trigger/tasks.py +1 -4
- infrahub/webhook/models.py +3 -0
- infrahub/workflows/initialization.py +1 -3
- infrahub_sdk/client.py +4 -4
- infrahub_sdk/config.py +17 -0
- infrahub_sdk/ctl/cli_commands.py +7 -1
- infrahub_sdk/ctl/generator.py +2 -2
- infrahub_sdk/generator.py +12 -66
- infrahub_sdk/operation.py +80 -0
- infrahub_sdk/protocols.py +12 -0
- infrahub_sdk/recorder.py +3 -0
- infrahub_sdk/schema/repository.py +4 -0
- infrahub_sdk/transforms.py +15 -27
- {infrahub_server-1.2.7.dist-info → infrahub_server-1.2.9.dist-info}/METADATA +2 -2
- {infrahub_server-1.2.7.dist-info → infrahub_server-1.2.9.dist-info}/RECORD +84 -78
- infrahub_testcontainers/container.py +1 -0
- infrahub_testcontainers/docker-compose.test.yml +5 -1
- infrahub/database/manager.py +0 -15
- /infrahub/{database/constants.py → constants/database.py} +0 -0
- {infrahub_server-1.2.7.dist-info → infrahub_server-1.2.9.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.2.7.dist-info → infrahub_server-1.2.9.dist-info}/WHEEL +0 -0
- {infrahub_server-1.2.7.dist-info → infrahub_server-1.2.9.dist-info}/entry_points.txt +0 -0
infrahub/core/manager.py
CHANGED
|
@@ -1229,20 +1229,31 @@ class NodeManager:
|
|
|
1229
1229
|
if not prefetch_relationships and not fields:
|
|
1230
1230
|
return
|
|
1231
1231
|
cardinality_one_identifiers_by_kind: dict[str, dict[str, RelationshipDirection]] | None = None
|
|
1232
|
-
|
|
1232
|
+
outbound_identifiers: set[str] | None = None
|
|
1233
|
+
inbound_identifiers: set[str] | None = None
|
|
1234
|
+
bidirectional_identifiers: set[str] | None = None
|
|
1233
1235
|
if not prefetch_relationships:
|
|
1234
1236
|
cardinality_one_identifiers_by_kind = _get_cardinality_one_identifiers_by_kind(
|
|
1235
1237
|
nodes=nodes_by_id.values(), fields=fields or {}
|
|
1236
1238
|
)
|
|
1237
|
-
|
|
1239
|
+
outbound_identifiers = set()
|
|
1240
|
+
inbound_identifiers = set()
|
|
1241
|
+
bidirectional_identifiers = set()
|
|
1238
1242
|
for identifier_direction_map in cardinality_one_identifiers_by_kind.values():
|
|
1239
|
-
|
|
1240
|
-
|
|
1243
|
+
for identifier, direction in identifier_direction_map.items():
|
|
1244
|
+
if direction is RelationshipDirection.OUTBOUND:
|
|
1245
|
+
outbound_identifiers.add(identifier)
|
|
1246
|
+
elif direction is RelationshipDirection.INBOUND:
|
|
1247
|
+
inbound_identifiers.add(identifier)
|
|
1248
|
+
elif direction is RelationshipDirection.BIDIR:
|
|
1249
|
+
bidirectional_identifiers.add(identifier)
|
|
1241
1250
|
|
|
1242
1251
|
query = await NodeListGetRelationshipsQuery.init(
|
|
1243
1252
|
db=db,
|
|
1244
1253
|
ids=list(nodes_by_id.keys()),
|
|
1245
|
-
|
|
1254
|
+
outbound_identifiers=None if outbound_identifiers is None else list(outbound_identifiers),
|
|
1255
|
+
inbound_identifiers=None if inbound_identifiers is None else list(inbound_identifiers),
|
|
1256
|
+
bidirectional_identifiers=None if bidirectional_identifiers is None else list(bidirectional_identifiers),
|
|
1246
1257
|
branch=branch,
|
|
1247
1258
|
at=at,
|
|
1248
1259
|
branch_agnostic=branch_agnostic,
|
|
@@ -28,6 +28,7 @@ from .m023_deduplicate_cardinality_one_relationships import Migration023
|
|
|
28
28
|
from .m024_missing_hierarchy_backfill import Migration024
|
|
29
29
|
from .m025_uniqueness_nulls import Migration025
|
|
30
30
|
from .m026_0000_prefix_fix import Migration026
|
|
31
|
+
from .m027_delete_isolated_nodes import Migration027
|
|
31
32
|
|
|
32
33
|
if TYPE_CHECKING:
|
|
33
34
|
from infrahub.core.root import Root
|
|
@@ -61,6 +62,7 @@ MIGRATIONS: list[type[GraphMigration | InternalSchemaMigration | ArbitraryMigrat
|
|
|
61
62
|
Migration024,
|
|
62
63
|
Migration025,
|
|
63
64
|
Migration026,
|
|
65
|
+
Migration027,
|
|
64
66
|
]
|
|
65
67
|
|
|
66
68
|
|
|
@@ -2,11 +2,12 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Sequence
|
|
4
4
|
|
|
5
|
+
from infrahub.constants.database import IndexType
|
|
5
6
|
from infrahub.core.migrations.shared import MigrationResult
|
|
6
7
|
from infrahub.core.query import Query # noqa: TC001
|
|
7
8
|
from infrahub.database import DatabaseType
|
|
8
|
-
from infrahub.database.constants import IndexType
|
|
9
9
|
from infrahub.database.index import IndexItem
|
|
10
|
+
from infrahub.database.neo4j import IndexManagerNeo4j
|
|
10
11
|
|
|
11
12
|
from ..shared import GraphMigration
|
|
12
13
|
|
|
@@ -29,13 +30,13 @@ class Migration014(GraphMigration):
|
|
|
29
30
|
if db.db_type != DatabaseType.NEO4J:
|
|
30
31
|
return result
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
try:
|
|
34
|
+
index_manager = IndexManagerNeo4j(db=db)
|
|
35
|
+
index_manager.init(nodes=[INDEX_TO_DELETE], rels=[])
|
|
36
|
+
await index_manager.drop()
|
|
37
|
+
except Exception as exc:
|
|
38
|
+
result.errors.append(str(exc))
|
|
39
|
+
return result
|
|
39
40
|
|
|
40
41
|
return result
|
|
41
42
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Sequence
|
|
4
|
+
|
|
5
|
+
from infrahub.core.migrations.shared import GraphMigration, MigrationResult
|
|
6
|
+
from infrahub.log import get_logger
|
|
7
|
+
|
|
8
|
+
from ...query import Query, QueryType
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from infrahub.database import InfrahubDatabase
|
|
12
|
+
|
|
13
|
+
log = get_logger()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DeleteIsolatedNodesQuery(Query):
|
|
17
|
+
name = "delete_isolated_nodes_query"
|
|
18
|
+
type = QueryType.WRITE
|
|
19
|
+
insert_return = False
|
|
20
|
+
|
|
21
|
+
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
|
|
22
|
+
query = """
|
|
23
|
+
MATCH p = (s: Node)-[r]-(d)
|
|
24
|
+
WHERE NOT exists((s)-[:IS_PART_OF]-(:Root))
|
|
25
|
+
DELETE r
|
|
26
|
+
|
|
27
|
+
WITH p
|
|
28
|
+
UNWIND nodes(p) AS n
|
|
29
|
+
MATCH (n)
|
|
30
|
+
WHERE NOT exists((n)--())
|
|
31
|
+
DELETE n
|
|
32
|
+
"""
|
|
33
|
+
self.add_to_query(query)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Migration027(GraphMigration):
|
|
37
|
+
"""
|
|
38
|
+
While deleting a branch containing some allocated nodes from a resource pool, relationship
|
|
39
|
+
between pool node and resource node might be agnostic (eg: for IPPrefixPool) and incorrectly deleted,
|
|
40
|
+
resulting in a node still linked to the resource pool but not linked to Root anymore.
|
|
41
|
+
This query deletes nodes not linked to Root and their relationships (supposed to be agnostic).
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
name: str = "027_deleted_isolated_nodes"
|
|
45
|
+
minimum_version: int = 26
|
|
46
|
+
queries: Sequence[type[Query]] = [DeleteIsolatedNodesQuery]
|
|
47
|
+
|
|
48
|
+
async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
|
|
49
|
+
result = MigrationResult()
|
|
50
|
+
return result
|
infrahub/core/protocols.py
CHANGED
infrahub/core/query/branch.py
CHANGED
|
@@ -51,23 +51,33 @@ class DeleteBranchRelationshipsQuery(Query):
|
|
|
51
51
|
super().__init__(**kwargs)
|
|
52
52
|
|
|
53
53
|
async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
54
|
+
query = """
|
|
55
|
+
MATCH (s)-[r1]-(d)
|
|
56
|
+
WHERE r1.branch = $branch_name
|
|
57
|
+
DELETE r1
|
|
58
|
+
|
|
59
|
+
WITH collect(DISTINCT s) + collect(DISTINCT d) AS nodes
|
|
60
|
+
|
|
61
|
+
// Collect node IDs for filtering
|
|
62
|
+
WITH nodes, [n in nodes | n.uuid] as nodes_uuids
|
|
63
|
+
|
|
64
|
+
// Also delete agnostic relationships that would not have been deleted above
|
|
65
|
+
MATCH (s2: Node)-[r2]-(d2)
|
|
66
|
+
WHERE NOT exists((s2)-[:IS_PART_OF]-(:Root))
|
|
67
|
+
AND s2.uuid IN nodes_uuids
|
|
68
|
+
DELETE r2
|
|
69
|
+
|
|
70
|
+
WITH nodes, collect(DISTINCT s2) + collect(DISTINCT d2) as additional_nodes
|
|
71
|
+
|
|
72
|
+
WITH nodes + additional_nodes as nodes
|
|
73
|
+
|
|
74
|
+
// Delete nodes that are no longer connected to any other nodes
|
|
75
|
+
UNWIND nodes AS n
|
|
76
|
+
WITH DISTINCT n
|
|
77
|
+
MATCH (n)
|
|
78
|
+
WHERE NOT exists((n)--())
|
|
79
|
+
DELETE n
|
|
80
|
+
"""
|
|
71
81
|
self.params["branch_name"] = self.branch_name
|
|
72
82
|
self.add_to_query(query)
|
|
73
83
|
|
infrahub/core/query/diff.py
CHANGED
|
@@ -9,6 +9,7 @@ from infrahub.core.timestamp import Timestamp
|
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
from infrahub.core.branch import Branch
|
|
12
|
+
from infrahub.core.diff.model.field_specifiers_map import NodeFieldSpecifierMap
|
|
12
13
|
from infrahub.database import InfrahubDatabase
|
|
13
14
|
|
|
14
15
|
|
|
@@ -106,8 +107,8 @@ class DiffCalculationQuery(DiffQuery):
|
|
|
106
107
|
self,
|
|
107
108
|
base_branch: Branch,
|
|
108
109
|
diff_branch_from_time: Timestamp,
|
|
109
|
-
current_node_field_specifiers:
|
|
110
|
-
new_node_field_specifiers:
|
|
110
|
+
current_node_field_specifiers: NodeFieldSpecifierMap | None = None,
|
|
111
|
+
new_node_field_specifiers: NodeFieldSpecifierMap | None = None,
|
|
111
112
|
**kwargs: Any,
|
|
112
113
|
):
|
|
113
114
|
self.base_branch = base_branch
|
|
@@ -231,10 +232,10 @@ class DiffNodePathsQuery(DiffCalculationQuery):
|
|
|
231
232
|
self.params.update(params_dict)
|
|
232
233
|
self.params.update(
|
|
233
234
|
{
|
|
234
|
-
"new_node_ids_list":
|
|
235
|
+
"new_node_ids_list": self.new_node_field_specifiers.get_uuids_list()
|
|
235
236
|
if self.new_node_field_specifiers
|
|
236
237
|
else None,
|
|
237
|
-
"current_node_ids_list":
|
|
238
|
+
"current_node_ids_list": self.current_node_field_specifiers.get_uuids_list()
|
|
238
239
|
if self.current_node_field_specifiers
|
|
239
240
|
else None,
|
|
240
241
|
}
|
|
@@ -371,15 +372,16 @@ class DiffFieldPathsQuery(DiffCalculationQuery):
|
|
|
371
372
|
|
|
372
373
|
self.params.update(
|
|
373
374
|
{
|
|
374
|
-
"
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
375
|
+
"current_node_ids_list": self.current_node_field_specifiers.get_uuids_list()
|
|
376
|
+
if self.current_node_field_specifiers
|
|
377
|
+
else None,
|
|
378
|
+
"new_node_ids_list": self.new_node_field_specifiers.get_uuids_list()
|
|
379
|
+
if self.new_node_field_specifiers
|
|
380
|
+
else None,
|
|
381
|
+
"current_node_field_specifiers_map": self.current_node_field_specifiers.get_uuid_field_names_map()
|
|
378
382
|
if self.current_node_field_specifiers is not None
|
|
379
383
|
else None,
|
|
380
|
-
"new_node_field_specifiers_map":
|
|
381
|
-
node_uuid: list(field_names) for node_uuid, field_names in self.new_node_field_specifiers.items()
|
|
382
|
-
}
|
|
384
|
+
"new_node_field_specifiers_map": self.new_node_field_specifiers.get_uuid_field_names_map()
|
|
383
385
|
if self.new_node_field_specifiers is not None
|
|
384
386
|
else None,
|
|
385
387
|
}
|
|
@@ -400,16 +402,16 @@ AND (r_root.to IS NULL OR diff_rel.branch <> r_root.branch OR r_root.to >= diff_
|
|
|
400
402
|
// node ID and field name filtering first pass
|
|
401
403
|
AND (
|
|
402
404
|
(
|
|
403
|
-
$
|
|
404
|
-
AND
|
|
405
|
+
$current_node_ids_list IS NOT NULL
|
|
406
|
+
AND p.uuid IN $current_node_ids_list
|
|
405
407
|
AND q.name IN $current_node_field_specifiers_map[p.uuid]
|
|
406
408
|
) OR (
|
|
407
|
-
$
|
|
408
|
-
AND
|
|
409
|
+
$new_node_ids_list IS NOT NULL
|
|
410
|
+
AND p.uuid IN $new_node_ids_list
|
|
409
411
|
AND q.name IN $new_node_field_specifiers_map[p.uuid]
|
|
410
412
|
) OR (
|
|
411
|
-
$
|
|
412
|
-
AND $
|
|
413
|
+
$new_node_ids_list IS NULL
|
|
414
|
+
AND $current_node_ids_list IS NULL
|
|
413
415
|
)
|
|
414
416
|
)
|
|
415
417
|
// node ID and field name filtering second pass
|
|
@@ -417,8 +419,12 @@ AND (
|
|
|
417
419
|
// time-based filters for nodes already included in the diff or fresh changes
|
|
418
420
|
(
|
|
419
421
|
(
|
|
420
|
-
(
|
|
421
|
-
|
|
422
|
+
(
|
|
423
|
+
$current_node_ids_list IS NOT NULL
|
|
424
|
+
AND p.uuid IN $current_node_ids_list
|
|
425
|
+
AND q.name IN $current_node_field_specifiers_map[p.uuid]
|
|
426
|
+
)
|
|
427
|
+
OR ($current_node_ids_list IS NULL AND $new_node_ids_list IS NULL)
|
|
422
428
|
)
|
|
423
429
|
AND (r_root.from < $from_time OR p.branch_support = $branch_agnostic)
|
|
424
430
|
AND (
|
|
@@ -428,7 +434,11 @@ AND (
|
|
|
428
434
|
)
|
|
429
435
|
// time-based filters for new nodes
|
|
430
436
|
OR (
|
|
431
|
-
(
|
|
437
|
+
(
|
|
438
|
+
$new_node_ids_list IS NOT NULL
|
|
439
|
+
AND p.uuid IN $new_node_ids_list
|
|
440
|
+
AND q.name IN $new_node_field_specifiers_map[p.uuid]
|
|
441
|
+
)
|
|
432
442
|
AND (r_root.from < $branch_from_time OR p.branch_support = $branch_agnostic)
|
|
433
443
|
AND (
|
|
434
444
|
($branch_from_time <= diff_rel.from < $to_time AND (diff_rel.to IS NULL OR diff_rel.to > $to_time))
|
|
@@ -454,7 +464,11 @@ WITH one_result[0] AS root, one_result[1] AS r_root, one_result[2] AS p, one_res
|
|
|
454
464
|
// Add correct from_time for row
|
|
455
465
|
// -------------------------------------
|
|
456
466
|
WITH root, r_root, p, diff_rel, q, has_more_data, CASE
|
|
457
|
-
WHEN
|
|
467
|
+
WHEN
|
|
468
|
+
$new_node_ids_list IS NOT NULL
|
|
469
|
+
AND p.uuid IN $new_node_ids_list
|
|
470
|
+
AND q.name IN $new_node_field_specifiers_map[p.uuid]
|
|
471
|
+
THEN $branch_from_time
|
|
458
472
|
ELSE $from_time
|
|
459
473
|
END AS row_from_time
|
|
460
474
|
// -------------------------------------
|
|
@@ -554,15 +568,16 @@ class DiffPropertyPathsQuery(DiffCalculationQuery):
|
|
|
554
568
|
|
|
555
569
|
self.params.update(
|
|
556
570
|
{
|
|
557
|
-
"
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
571
|
+
"current_node_ids_list": self.current_node_field_specifiers.get_uuids_list()
|
|
572
|
+
if self.current_node_field_specifiers
|
|
573
|
+
else None,
|
|
574
|
+
"new_node_ids_list": self.new_node_field_specifiers.get_uuids_list()
|
|
575
|
+
if self.new_node_field_specifiers
|
|
576
|
+
else None,
|
|
577
|
+
"current_node_field_specifiers_map": self.current_node_field_specifiers.get_uuid_field_names_map()
|
|
561
578
|
if self.current_node_field_specifiers is not None
|
|
562
579
|
else None,
|
|
563
|
-
"new_node_field_specifiers_map":
|
|
564
|
-
node_uuid: list(field_names) for node_uuid, field_names in self.new_node_field_specifiers.items()
|
|
565
|
-
}
|
|
580
|
+
"new_node_field_specifiers_map": self.new_node_field_specifiers.get_uuid_field_names_map()
|
|
566
581
|
if self.new_node_field_specifiers is not None
|
|
567
582
|
else None,
|
|
568
583
|
}
|
|
@@ -580,16 +595,16 @@ AND type(r_node) IN ["HAS_ATTRIBUTE", "IS_RELATED"]
|
|
|
580
595
|
// node ID and field name filtering first pass
|
|
581
596
|
AND (
|
|
582
597
|
(
|
|
583
|
-
$
|
|
584
|
-
AND
|
|
598
|
+
$current_node_ids_list IS NOT NULL
|
|
599
|
+
AND n.uuid IN $current_node_ids_list
|
|
585
600
|
AND p.name IN $current_node_field_specifiers_map[n.uuid]
|
|
586
601
|
) OR (
|
|
587
|
-
$
|
|
588
|
-
AND
|
|
602
|
+
$new_node_ids_list IS NOT NULL
|
|
603
|
+
AND n.uuid IN $new_node_ids_list
|
|
589
604
|
AND p.name IN $new_node_field_specifiers_map[n.uuid]
|
|
590
605
|
) OR (
|
|
591
|
-
$
|
|
592
|
-
AND $
|
|
606
|
+
$new_node_ids_list IS NULL
|
|
607
|
+
AND $current_node_ids_list IS NULL
|
|
593
608
|
)
|
|
594
609
|
)
|
|
595
610
|
// node ID and field name filtering second pass
|
|
@@ -597,8 +612,12 @@ AND (
|
|
|
597
612
|
// time-based filters for nodes already included in the diff or fresh changes
|
|
598
613
|
(
|
|
599
614
|
(
|
|
600
|
-
(
|
|
601
|
-
|
|
615
|
+
(
|
|
616
|
+
$current_node_ids_list IS NOT NULL
|
|
617
|
+
AND n.uuid IN $current_node_ids_list
|
|
618
|
+
AND p.name IN $current_node_field_specifiers_map[n.uuid]
|
|
619
|
+
)
|
|
620
|
+
OR ($current_node_ids_list IS NULL AND $new_node_ids_list IS NULL)
|
|
602
621
|
)
|
|
603
622
|
AND (
|
|
604
623
|
($from_time <= diff_rel.from < $to_time AND (diff_rel.to IS NULL OR diff_rel.to > $to_time))
|
|
@@ -612,7 +631,11 @@ AND (
|
|
|
612
631
|
)
|
|
613
632
|
// time-based filters for new nodes
|
|
614
633
|
OR (
|
|
615
|
-
(
|
|
634
|
+
(
|
|
635
|
+
$new_node_ids_list IS NOT NULL
|
|
636
|
+
AND n.uuid IN $new_node_ids_list
|
|
637
|
+
AND p.name IN $new_node_field_specifiers_map[n.uuid]
|
|
638
|
+
)
|
|
616
639
|
AND (
|
|
617
640
|
($branch_from_time <= diff_rel.from < $to_time AND (diff_rel.to IS NULL OR diff_rel.to > $to_time))
|
|
618
641
|
OR ($branch_from_time <= diff_rel.to < $to_time)
|
|
@@ -667,7 +690,11 @@ WITH one_result[0] AS diff_rel_path, one_result[1] AS r_root, one_result[2] AS n
|
|
|
667
690
|
// Add correct from_time for row
|
|
668
691
|
// -------------------------------------
|
|
669
692
|
WITH diff_rel_path, r_root, n, r_node, p, diff_rel, has_more_data, CASE
|
|
670
|
-
WHEN
|
|
693
|
+
WHEN
|
|
694
|
+
$new_node_ids_list IS NOT NULL
|
|
695
|
+
AND n.uuid IN $new_node_ids_list
|
|
696
|
+
AND p.name IN $new_node_field_specifiers_map[n.uuid]
|
|
697
|
+
THEN $branch_from_time
|
|
671
698
|
ELSE $from_time
|
|
672
699
|
END AS row_from_time
|
|
673
700
|
WITH diff_rel_path, r_root, n, r_node, p, diff_rel, has_more_data, row_from_time
|
infrahub/core/query/node.py
CHANGED
|
@@ -92,6 +92,7 @@ class NodeAttributesFromDB:
|
|
|
92
92
|
class PeerInfo:
|
|
93
93
|
uuid: str
|
|
94
94
|
kind: str
|
|
95
|
+
labels: frozenset[str]
|
|
95
96
|
|
|
96
97
|
|
|
97
98
|
class NodeQuery(Query):
|
|
@@ -649,51 +650,118 @@ class NodeListGetRelationshipsQuery(Query):
|
|
|
649
650
|
type: QueryType = QueryType.READ
|
|
650
651
|
insert_return: bool = False
|
|
651
652
|
|
|
652
|
-
def __init__(
|
|
653
|
+
def __init__(
|
|
654
|
+
self,
|
|
655
|
+
ids: list[str],
|
|
656
|
+
outbound_identifiers: list[str] | None = None,
|
|
657
|
+
inbound_identifiers: list[str] | None = None,
|
|
658
|
+
bidirectional_identifiers: list[str] | None = None,
|
|
659
|
+
**kwargs,
|
|
660
|
+
):
|
|
653
661
|
self.ids = ids
|
|
654
|
-
self.
|
|
662
|
+
self.outbound_identifiers = outbound_identifiers
|
|
663
|
+
self.inbound_identifiers = inbound_identifiers
|
|
664
|
+
self.bidirectional_identifiers = bidirectional_identifiers
|
|
655
665
|
super().__init__(**kwargs)
|
|
656
666
|
|
|
657
667
|
async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG002
|
|
658
668
|
self.params["ids"] = self.ids
|
|
659
|
-
self.params["
|
|
669
|
+
self.params["outbound_identifiers"] = self.outbound_identifiers
|
|
670
|
+
self.params["inbound_identifiers"] = self.inbound_identifiers
|
|
671
|
+
self.params["bidirectional_identifiers"] = self.bidirectional_identifiers
|
|
660
672
|
|
|
661
673
|
rels_filter, rels_params = self.branch.get_query_filter_path(at=self.at, branch_agnostic=self.branch_agnostic)
|
|
662
674
|
self.params.update(rels_params)
|
|
663
675
|
|
|
664
676
|
query = """
|
|
665
677
|
MATCH (n:Node) WHERE n.uuid IN $ids
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
678
|
+
CALL {
|
|
679
|
+
WITH n
|
|
680
|
+
MATCH (n)<-[:IS_RELATED]-(rel:Relationship)<-[:IS_RELATED]-(peer)
|
|
681
|
+
WHERE ($inbound_identifiers IS NULL OR rel.name in $inbound_identifiers)
|
|
682
|
+
AND n.uuid <> peer.uuid
|
|
683
|
+
WITH DISTINCT n, rel, peer
|
|
684
|
+
CALL {
|
|
685
|
+
WITH n, rel, peer
|
|
686
|
+
MATCH (n)<-[r:IS_RELATED]-(rel)
|
|
687
|
+
WHERE (%(filters)s)
|
|
688
|
+
WITH n, rel, peer, r
|
|
689
|
+
ORDER BY r.from DESC
|
|
690
|
+
LIMIT 1
|
|
691
|
+
WITH n, rel, peer, r AS r1
|
|
692
|
+
WHERE r1.status = "active"
|
|
693
|
+
MATCH (rel)<-[r:IS_RELATED]-(peer)
|
|
694
|
+
WHERE (%(filters)s)
|
|
695
|
+
WITH r1, r
|
|
696
|
+
ORDER BY r.from DESC
|
|
697
|
+
LIMIT 1
|
|
698
|
+
WITH r1, r AS r2
|
|
699
|
+
WHERE r2.status = "active"
|
|
700
|
+
RETURN 1 AS is_active
|
|
701
|
+
}
|
|
702
|
+
RETURN n.uuid AS n_uuid, rel.name AS rel_name, peer.uuid AS peer_uuid, "inbound" as direction
|
|
703
|
+
UNION
|
|
704
|
+
WITH n
|
|
705
|
+
MATCH (n)-[:IS_RELATED]->(rel:Relationship)-[:IS_RELATED]->(peer)
|
|
706
|
+
WHERE ($outbound_identifiers IS NULL OR rel.name in $outbound_identifiers)
|
|
707
|
+
AND n.uuid <> peer.uuid
|
|
708
|
+
WITH DISTINCT n, rel, peer
|
|
709
|
+
CALL {
|
|
710
|
+
WITH n, rel, peer
|
|
711
|
+
MATCH (n)-[r:IS_RELATED]->(rel)
|
|
712
|
+
WHERE (%(filters)s)
|
|
713
|
+
WITH n, rel, peer, r
|
|
714
|
+
ORDER BY r.from DESC
|
|
715
|
+
LIMIT 1
|
|
716
|
+
WITH n, rel, peer, r AS r1
|
|
717
|
+
WHERE r1.status = "active"
|
|
718
|
+
MATCH (rel)-[r:IS_RELATED]->(peer)
|
|
719
|
+
WHERE (%(filters)s)
|
|
720
|
+
WITH r1, r
|
|
721
|
+
ORDER BY r.from DESC
|
|
722
|
+
LIMIT 1
|
|
723
|
+
WITH r1, r AS r2
|
|
724
|
+
WHERE r2.status = "active"
|
|
725
|
+
RETURN 1 AS is_active
|
|
726
|
+
}
|
|
727
|
+
RETURN n.uuid AS n_uuid, rel.name AS rel_name, peer.uuid AS peer_uuid, "outbound" as direction
|
|
728
|
+
UNION
|
|
729
|
+
WITH n
|
|
730
|
+
MATCH (n)-[:IS_RELATED]->(rel:Relationship)<-[:IS_RELATED]-(peer)
|
|
731
|
+
WHERE ($bidirectional_identifiers IS NULL OR rel.name in $bidirectional_identifiers)
|
|
732
|
+
AND n.uuid <> peer.uuid
|
|
733
|
+
WITH DISTINCT n, rel, peer
|
|
734
|
+
CALL {
|
|
735
|
+
WITH n, rel, peer
|
|
736
|
+
MATCH (n)-[r:IS_RELATED]->(rel)
|
|
737
|
+
WHERE (%(filters)s)
|
|
738
|
+
WITH n, rel, peer, r
|
|
739
|
+
ORDER BY r.from DESC
|
|
740
|
+
LIMIT 1
|
|
741
|
+
WITH n, rel, peer, r AS r1
|
|
742
|
+
WHERE r1.status = "active"
|
|
743
|
+
MATCH (rel)<-[r:IS_RELATED]-(peer)
|
|
744
|
+
WHERE (%(filters)s)
|
|
745
|
+
WITH r1, r
|
|
746
|
+
ORDER BY r.from DESC
|
|
747
|
+
LIMIT 1
|
|
748
|
+
WITH r1, r AS r2
|
|
749
|
+
WHERE r2.status = "active"
|
|
750
|
+
RETURN 1 AS is_active
|
|
751
|
+
}
|
|
752
|
+
RETURN n.uuid AS n_uuid, rel.name AS rel_name, peer.uuid AS peer_uuid, "bidirectional" as direction
|
|
753
|
+
}
|
|
754
|
+
RETURN DISTINCT n_uuid, rel_name, peer_uuid, direction
|
|
685
755
|
""" % {"filters": rels_filter}
|
|
686
|
-
|
|
687
756
|
self.add_to_query(query)
|
|
688
|
-
|
|
689
|
-
self.return_labels = ["n", "rel", "peer", "r1", "r2", "direction"]
|
|
757
|
+
self.return_labels = ["n_uuid", "rel_name", "peer_uuid", "direction"]
|
|
690
758
|
|
|
691
759
|
def get_peers_group_by_node(self) -> GroupedPeerNodes:
|
|
692
760
|
gpn = GroupedPeerNodes()
|
|
693
|
-
for result in self.
|
|
694
|
-
node_id = result.get("
|
|
695
|
-
rel_name = result.get("
|
|
696
|
-
peer_id = result.get("
|
|
761
|
+
for result in self.get_results():
|
|
762
|
+
node_id = result.get("n_uuid")
|
|
763
|
+
rel_name = result.get("rel_name")
|
|
764
|
+
peer_id = result.get("peer_uuid")
|
|
697
765
|
direction = str(result.get("direction"))
|
|
698
766
|
direction_enum = {
|
|
699
767
|
"inbound": RelationshipDirection.INBOUND,
|
|
@@ -1338,7 +1406,7 @@ class NodeGetHierarchyQuery(Query):
|
|
|
1338
1406
|
|
|
1339
1407
|
super().__init__(**kwargs)
|
|
1340
1408
|
|
|
1341
|
-
async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
|
|
1409
|
+
async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002,PLR0915
|
|
1342
1410
|
hierarchy_schema = self.node_schema.get_hierarchy_schema(db=db, branch=self.branch)
|
|
1343
1411
|
branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string())
|
|
1344
1412
|
self.params.update(branch_params)
|
|
@@ -1371,6 +1439,10 @@ class NodeGetHierarchyQuery(Query):
|
|
|
1371
1439
|
UNWIND peers_with_duplicates AS pwd
|
|
1372
1440
|
RETURN DISTINCT pwd AS peer
|
|
1373
1441
|
}
|
|
1442
|
+
""" % {"filter": filter_str, "branch_filter": branch_filter}
|
|
1443
|
+
|
|
1444
|
+
if not self.branch.is_default:
|
|
1445
|
+
query += """
|
|
1374
1446
|
CALL {
|
|
1375
1447
|
WITH n, peer
|
|
1376
1448
|
MATCH path = (n)%(filter)s(peer)
|
|
@@ -1381,10 +1453,14 @@ class NodeGetHierarchyQuery(Query):
|
|
|
1381
1453
|
LIMIT 1
|
|
1382
1454
|
}
|
|
1383
1455
|
WITH peer1 as peer, is_active
|
|
1384
|
-
|
|
1456
|
+
""" % {"filter": filter_str, "branch_filter": branch_filter, "with_clause": with_clause}
|
|
1457
|
+
else:
|
|
1458
|
+
query += """
|
|
1459
|
+
WITH peer
|
|
1460
|
+
"""
|
|
1385
1461
|
|
|
1386
1462
|
self.add_to_query(query)
|
|
1387
|
-
where_clause = ["is_active = TRUE"]
|
|
1463
|
+
where_clause = ["is_active = TRUE"] if not self.branch.is_default else []
|
|
1388
1464
|
|
|
1389
1465
|
clean_filters = extract_field_filters(field_name=self.direction.value, filters=self.filters)
|
|
1390
1466
|
|
|
@@ -1394,7 +1470,8 @@ class NodeGetHierarchyQuery(Query):
|
|
|
1394
1470
|
if clean_filters.get("id", None):
|
|
1395
1471
|
self.params["peer_ids"].append(clean_filters.get("id"))
|
|
1396
1472
|
|
|
1397
|
-
|
|
1473
|
+
if where_clause:
|
|
1474
|
+
self.add_to_query("WHERE " + " AND ".join(where_clause))
|
|
1398
1475
|
|
|
1399
1476
|
self.return_labels = ["peer"]
|
|
1400
1477
|
|
|
@@ -1477,4 +1554,5 @@ class NodeGetHierarchyQuery(Query):
|
|
|
1477
1554
|
yield PeerInfo(
|
|
1478
1555
|
uuid=peer_node.get("uuid"),
|
|
1479
1556
|
kind=peer_node.get("kind"),
|
|
1557
|
+
labels=peer_node.labels,
|
|
1480
1558
|
)
|
|
@@ -73,12 +73,21 @@ class RelationshipPeerData:
|
|
|
73
73
|
source_id: UUID
|
|
74
74
|
"""UUID of the Source Node."""
|
|
75
75
|
|
|
76
|
+
source_kind: str
|
|
77
|
+
"""Kind of the Source Node."""
|
|
78
|
+
|
|
79
|
+
source_labels: frozenset[str]
|
|
80
|
+
"""Labels of the Source Node."""
|
|
81
|
+
|
|
76
82
|
peer_id: UUID
|
|
77
83
|
"""UUID of the Peer Node."""
|
|
78
84
|
|
|
79
85
|
peer_kind: str
|
|
80
86
|
"""Kind of the Peer Node."""
|
|
81
87
|
|
|
88
|
+
peer_labels: frozenset[str]
|
|
89
|
+
"""Labels of the Peer Node."""
|
|
90
|
+
|
|
82
91
|
properties: dict[str, FlagPropertyData | NodePropertyData]
|
|
83
92
|
"""UUID of the Relationship Node."""
|
|
84
93
|
|
|
@@ -752,10 +761,15 @@ class RelationshipGetPeerQuery(Query):
|
|
|
752
761
|
def get_peers(self) -> Generator[RelationshipPeerData, None, None]:
|
|
753
762
|
for result in self.get_results_group_by(("peer", "uuid"), ("source_node", "uuid")):
|
|
754
763
|
rels = result.get("rels")
|
|
764
|
+
source_node = result.get_node("source_node")
|
|
765
|
+
peer_node = result.get_node("peer")
|
|
755
766
|
data = RelationshipPeerData(
|
|
756
|
-
source_id=
|
|
757
|
-
|
|
758
|
-
|
|
767
|
+
source_id=source_node.get("uuid"),
|
|
768
|
+
source_kind=source_node.get("kind"),
|
|
769
|
+
source_labels=source_node.labels,
|
|
770
|
+
peer_id=peer_node.get("uuid"),
|
|
771
|
+
peer_kind=peer_node.get("kind"),
|
|
772
|
+
peer_labels=peer_node.labels,
|
|
759
773
|
rel_node_db_id=result.get("rl").element_id,
|
|
760
774
|
rel_node_id=result.get("rl").get("uuid"),
|
|
761
775
|
updated_at=rels[0]["from"],
|