infrahub-server 1.2.8__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.
Files changed (50) hide show
  1. infrahub/cli/db.py +14 -5
  2. infrahub/computed_attribute/tasks.py +33 -12
  3. infrahub/core/branch/tasks.py +0 -2
  4. infrahub/core/diff/calculator.py +4 -3
  5. infrahub/core/diff/combiner.py +1 -2
  6. infrahub/core/diff/coordinator.py +44 -28
  7. infrahub/core/diff/data_check_synchronizer.py +3 -2
  8. infrahub/core/diff/enricher/hierarchy.py +38 -27
  9. infrahub/core/diff/ipam_diff_parser.py +5 -4
  10. infrahub/core/diff/merger/merger.py +20 -18
  11. infrahub/core/diff/model/field_specifiers_map.py +64 -0
  12. infrahub/core/diff/model/path.py +55 -58
  13. infrahub/core/diff/parent_node_adder.py +14 -16
  14. infrahub/core/diff/query/drop_nodes.py +42 -0
  15. infrahub/core/diff/query/field_specifiers.py +8 -7
  16. infrahub/core/diff/query/filters.py +15 -1
  17. infrahub/core/diff/query/save.py +3 -0
  18. infrahub/core/diff/query_parser.py +49 -52
  19. infrahub/core/diff/repository/deserializer.py +36 -23
  20. infrahub/core/diff/repository/repository.py +31 -12
  21. infrahub/core/graph/__init__.py +1 -1
  22. infrahub/core/initialization.py +23 -7
  23. infrahub/core/migrations/graph/__init__.py +2 -0
  24. infrahub/core/migrations/graph/m014_remove_index_attr_value.py +4 -2
  25. infrahub/core/migrations/graph/m027_delete_isolated_nodes.py +50 -0
  26. infrahub/core/query/branch.py +27 -17
  27. infrahub/core/query/diff.py +65 -38
  28. infrahub/core/query/node.py +15 -4
  29. infrahub/core/query/relationship.py +17 -3
  30. infrahub/core/query/subquery.py +2 -2
  31. infrahub/core/schema/schema_branch.py +3 -0
  32. infrahub/core/validators/uniqueness/query.py +24 -8
  33. infrahub/database/__init__.py +0 -7
  34. infrahub/database/memgraph.py +0 -12
  35. infrahub/database/neo4j.py +0 -12
  36. infrahub/graphql/mutations/computed_attribute.py +5 -1
  37. infrahub/graphql/queries/diff/tree.py +2 -1
  38. infrahub/server.py +1 -5
  39. infrahub/trigger/models.py +11 -1
  40. infrahub/trigger/setup.py +38 -13
  41. infrahub/trigger/tasks.py +1 -4
  42. infrahub/workflows/initialization.py +1 -3
  43. {infrahub_server-1.2.8.dist-info → infrahub_server-1.2.9.dist-info}/METADATA +1 -1
  44. {infrahub_server-1.2.8.dist-info → infrahub_server-1.2.9.dist-info}/RECORD +49 -47
  45. infrahub_testcontainers/container.py +1 -0
  46. infrahub_testcontainers/docker-compose.test.yml +3 -1
  47. infrahub/database/manager.py +0 -15
  48. {infrahub_server-1.2.8.dist-info → infrahub_server-1.2.9.dist-info}/LICENSE.txt +0 -0
  49. {infrahub_server-1.2.8.dist-info → infrahub_server-1.2.9.dist-info}/WHEEL +0 -0
  50. {infrahub_server-1.2.8.dist-info → infrahub_server-1.2.9.dist-info}/entry_points.txt +0 -0
@@ -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
- if config.SETTINGS.database.db_type == config.DatabaseType.MEMGRAPH:
55
- query = """
56
- MATCH p = (s)-[r]-(d)
57
- WHERE r.branch = $branch_name
58
- DELETE r
59
- """
60
- else:
61
- query = """
62
- MATCH p = (s)-[r]-(d)
63
- WHERE r.branch = $branch_name
64
- DELETE r
65
- WITH *
66
- UNWIND nodes(p) AS n
67
- MATCH (n)
68
- WHERE NOT exists((n)--())
69
- DELETE n
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
 
@@ -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: dict[str, set[str]] | None = None,
110
- new_node_field_specifiers: dict[str, set[str]] | None = None,
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": list(self.new_node_field_specifiers.keys())
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": list(self.current_node_field_specifiers.keys())
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
- "current_node_field_specifiers_map": {
375
- node_uuid: list(field_names)
376
- for node_uuid, field_names in self.current_node_field_specifiers.items()
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
- $current_node_field_specifiers_map IS NOT NULL
404
- AND $current_node_field_specifiers_map[p.uuid] IS NOT NULL
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
- $new_node_field_specifiers_map IS NOT NULL
408
- AND $new_node_field_specifiers_map[p.uuid] IS NOT NULL
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
- $current_node_field_specifiers_map IS NULL
412
- AND $new_node_field_specifiers_map IS NULL
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
- ($current_node_field_specifiers_map IS NOT NULL AND q.name IN $current_node_field_specifiers_map[p.uuid])
421
- OR ($current_node_field_specifiers_map IS NULL AND $new_node_field_specifiers_map IS NULL)
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
- ($new_node_field_specifiers_map IS NOT NULL AND q.name IN $new_node_field_specifiers_map[p.uuid])
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 $new_node_field_specifiers_map IS NOT NULL AND q.name IN $new_node_field_specifiers_map[p.uuid] THEN $branch_from_time
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
- "current_node_field_specifiers_map": {
558
- node_uuid: list(field_names)
559
- for node_uuid, field_names in self.current_node_field_specifiers.items()
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
- $current_node_field_specifiers_map IS NOT NULL
584
- AND $current_node_field_specifiers_map[n.uuid] IS NOT NULL
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
- $new_node_field_specifiers_map IS NOT NULL
588
- AND $new_node_field_specifiers_map[n.uuid] IS NOT NULL
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
- $current_node_field_specifiers_map IS NULL
592
- AND $new_node_field_specifiers_map IS NULL
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
- ($current_node_field_specifiers_map IS NOT NULL AND p.name IN $current_node_field_specifiers_map[n.uuid])
601
- OR ($current_node_field_specifiers_map IS NULL AND $new_node_field_specifiers_map IS NULL)
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
- ($new_node_field_specifiers_map IS NOT NULL AND p.name IN $new_node_field_specifiers_map[n.uuid])
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 $new_node_field_specifiers_map IS NOT NULL AND p.name IN $new_node_field_specifiers_map[n.uuid] THEN $branch_from_time
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
@@ -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):
@@ -1405,7 +1406,7 @@ class NodeGetHierarchyQuery(Query):
1405
1406
 
1406
1407
  super().__init__(**kwargs)
1407
1408
 
1408
- 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
1409
1410
  hierarchy_schema = self.node_schema.get_hierarchy_schema(db=db, branch=self.branch)
1410
1411
  branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string())
1411
1412
  self.params.update(branch_params)
@@ -1438,6 +1439,10 @@ class NodeGetHierarchyQuery(Query):
1438
1439
  UNWIND peers_with_duplicates AS pwd
1439
1440
  RETURN DISTINCT pwd AS peer
1440
1441
  }
1442
+ """ % {"filter": filter_str, "branch_filter": branch_filter}
1443
+
1444
+ if not self.branch.is_default:
1445
+ query += """
1441
1446
  CALL {
1442
1447
  WITH n, peer
1443
1448
  MATCH path = (n)%(filter)s(peer)
@@ -1448,10 +1453,14 @@ class NodeGetHierarchyQuery(Query):
1448
1453
  LIMIT 1
1449
1454
  }
1450
1455
  WITH peer1 as peer, is_active
1451
- """ % {"filter": filter_str, "branch_filter": branch_filter, "with_clause": with_clause}
1456
+ """ % {"filter": filter_str, "branch_filter": branch_filter, "with_clause": with_clause}
1457
+ else:
1458
+ query += """
1459
+ WITH peer
1460
+ """
1452
1461
 
1453
1462
  self.add_to_query(query)
1454
- where_clause = ["is_active = TRUE"]
1463
+ where_clause = ["is_active = TRUE"] if not self.branch.is_default else []
1455
1464
 
1456
1465
  clean_filters = extract_field_filters(field_name=self.direction.value, filters=self.filters)
1457
1466
 
@@ -1461,7 +1470,8 @@ class NodeGetHierarchyQuery(Query):
1461
1470
  if clean_filters.get("id", None):
1462
1471
  self.params["peer_ids"].append(clean_filters.get("id"))
1463
1472
 
1464
- self.add_to_query("WHERE " + " AND ".join(where_clause))
1473
+ if where_clause:
1474
+ self.add_to_query("WHERE " + " AND ".join(where_clause))
1465
1475
 
1466
1476
  self.return_labels = ["peer"]
1467
1477
 
@@ -1544,4 +1554,5 @@ class NodeGetHierarchyQuery(Query):
1544
1554
  yield PeerInfo(
1545
1555
  uuid=peer_node.get("uuid"),
1546
1556
  kind=peer_node.get("kind"),
1557
+ labels=peer_node.labels,
1547
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=result.get_node("source_node").get("uuid"),
757
- peer_id=result.get_node("peer").get("uuid"),
758
- peer_kind=result.get_node("peer").get("kind"),
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"],
@@ -57,7 +57,7 @@ async def build_subquery_filter(
57
57
  params.update(field_params)
58
58
 
59
59
  field_where.append("all(r IN relationships(path) WHERE (%s))" % branch_filter)
60
- filter_str = f"({node_alias})" + "".join([str(item) for item in field_filter])
60
+ filter_str = f"({node_alias}:Node {{uuid: {node_alias}.uuid}})" + "".join([str(item) for item in field_filter])
61
61
  where_str = " AND ".join(field_where)
62
62
  branch_level_str = "reduce(br_lvl = 0, r in relationships(path) | br_lvl + r.branch_level)"
63
63
  froms_str = db.render_list_comprehension(items="relationships(path)", item_name="from")
@@ -138,7 +138,7 @@ async def build_subquery_order(
138
138
  field_filter[-1].name = "last"
139
139
 
140
140
  field_where.append("all(r IN relationships(path) WHERE (%s))" % branch_filter)
141
- filter_str = f"({node_alias})" + "".join([str(item) for item in field_filter])
141
+ filter_str = f"({node_alias}:Node {{uuid: {node_alias}.uuid}})" + "".join([str(item) for item in field_filter])
142
142
  where_str = " AND ".join(field_where)
143
143
  branch_level_str = "reduce(br_lvl = 0, r in relationships(path) | br_lvl + r.branch_level)"
144
144
  froms_str = db.render_list_comprehension(items="relationships(path)", item_name="from")
@@ -2050,6 +2050,9 @@ class SchemaBranch:
2050
2050
 
2051
2051
  identified.add(node_schema)
2052
2052
 
2053
+ if node_schema.is_node_schema:
2054
+ identified.update([self.get(name=kind, duplicate=False) for kind in node_schema.inherit_from])
2055
+
2053
2056
  for relationship in node_schema.relationships:
2054
2057
  if (
2055
2058
  relationship.peer in [InfrahubKind.GENERICGROUP, InfrahubKind.PROFILE]
@@ -30,7 +30,7 @@ class NodeUniqueAttributeConstraintQuery(Query):
30
30
  def get_context(self) -> dict[str, str]:
31
31
  return {"kind": self.query_request.kind}
32
32
 
33
- async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
33
+ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002,PLR0915
34
34
  branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string(), is_isolated=False)
35
35
  self.params.update(branch_params)
36
36
  from_times = db.render_list_comprehension(items="relationships(potential_path)", item_name="from")
@@ -105,16 +105,28 @@ class NodeUniqueAttributeConstraintQuery(Query):
105
105
  attr_paths_subquery = """
106
106
  MATCH attr_path = (start_node:%(node_kind)s)-[:HAS_ATTRIBUTE]->(attr:Attribute)-[r:HAS_VALUE]->(attr_value:AttributeValue)
107
107
  WHERE attr.name in $attribute_names
108
- AND ([attr.name, type(r)] in $attr_paths
109
- OR (attr_value.value in $attr_values AND [attr.name, type(r), attr_value.value] in $attr_paths_with_value))
108
+ AND [attr.name, type(r)] in $attr_paths
110
109
  RETURN start_node, attr_path as potential_path, NULL as rel_identifier, attr.name as potential_attr, attr_value.value as potential_attr_value
111
110
  """ % {"node_kind": self.query_request.kind}
112
111
 
113
- relationship_attr_paths_with_value_subquery = """
112
+ attr_paths_with_value_subquery = """
113
+ MATCH attr_path = (start_node:%(node_kind)s)-[:HAS_ATTRIBUTE]->(attr:Attribute)-[r:HAS_VALUE]->(attr_value:AttributeValue)
114
+ WHERE attr.name in $attribute_names AND attr_value.value in $attr_values
115
+ AND [attr.name, type(r), attr_value.value] in $attr_paths_with_value
116
+ RETURN start_node, attr_path as potential_path, NULL as rel_identifier, attr.name as potential_attr, attr_value.value as potential_attr_value
117
+ """ % {"node_kind": self.query_request.kind}
118
+
119
+ relationship_attr_paths_subquery = """
114
120
  MATCH rel_path = (start_node:%(node_kind)s)-[:IS_RELATED]-(relationship_node:Relationship)-[:IS_RELATED]-(related_n:Node)-[:HAS_ATTRIBUTE]->(rel_attr:Attribute)-[:HAS_VALUE]->(rel_attr_value:AttributeValue)
115
121
  WHERE relationship_node.name in $relationship_names
116
- AND ([relationship_node.name, rel_attr.name] in $relationship_attr_paths
117
- OR (rel_attr_value.value in $relationship_attr_values AND [relationship_node.name, rel_attr.name, rel_attr_value.value] in $relationship_attr_paths_with_value))
122
+ AND [relationship_node.name, rel_attr.name] in $relationship_attr_paths
123
+ RETURN start_node, rel_path as potential_path, relationship_node.name as rel_identifier, rel_attr.name as potential_attr, rel_attr_value.value as potential_attr_value
124
+ """ % {"node_kind": self.query_request.kind}
125
+
126
+ relationship_attr_paths_with_value_subquery = """
127
+ MATCH rel_path = (start_node:%(node_kind)s)-[:IS_RELATED]-(relationship_node:Relationship)-[:IS_RELATED]-(related_n:Node)-[:HAS_ATTRIBUTE]->(rel_attr:Attribute)-[:HAS_VALUE]->(rel_attr_value:AttributeValue)
128
+ WHERE relationship_node.name in $relationship_names AND rel_attr_value.value in $relationship_attr_values
129
+ AND [relationship_node.name, rel_attr.name, rel_attr_value.value] in $relationship_attr_paths_with_value
118
130
  RETURN start_node, rel_path as potential_path, relationship_node.name as rel_identifier, rel_attr.name as potential_attr, rel_attr_value.value as potential_attr_value
119
131
  """ % {"node_kind": self.query_request.kind}
120
132
 
@@ -130,9 +142,13 @@ class NodeUniqueAttributeConstraintQuery(Query):
130
142
  }
131
143
 
132
144
  select_subqueries = []
133
- if attr_paths or attr_paths_with_value:
145
+ if attr_paths:
134
146
  select_subqueries.append(attr_paths_subquery)
135
- if relationship_attr_paths_with_value or relationship_attr_paths:
147
+ if attr_paths_with_value:
148
+ select_subqueries.append(attr_paths_with_value_subquery)
149
+ if relationship_attr_paths:
150
+ select_subqueries.append(relationship_attr_paths_subquery)
151
+ if relationship_attr_paths_with_value:
136
152
  select_subqueries.append(relationship_attr_paths_with_value_subquery)
137
153
  if relationship_only_attr_paths:
138
154
  select_subqueries.append(relationship_only_attr_paths_subquery)
@@ -33,9 +33,7 @@ from infrahub.exceptions import DatabaseError
33
33
  from infrahub.log import get_logger
34
34
  from infrahub.utils import InfrahubStringEnum
35
35
 
36
- from .memgraph import DatabaseManagerMemgraph
37
36
  from .metrics import CONNECTION_POOL_USAGE, QUERY_EXECUTION_METRICS, TRANSACTION_RETRIES
38
- from .neo4j import DatabaseManagerNeo4j
39
37
 
40
38
  if TYPE_CHECKING:
41
39
  from types import TracebackType
@@ -158,11 +156,6 @@ class InfrahubDatabase:
158
156
  else:
159
157
  self.db_type = config.SETTINGS.database.db_type
160
158
 
161
- if self.db_type == DatabaseType.NEO4J:
162
- self.manager = DatabaseManagerNeo4j(db=self)
163
- elif self.db_type == DatabaseType.MEMGRAPH:
164
- self.manager = DatabaseManagerMemgraph(db=self)
165
-
166
159
  def __del__(self) -> None:
167
160
  if not self._session or not self._is_session_local or self._session.closed():
168
161
  return
@@ -1,14 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
4
-
5
3
  from infrahub.constants.database import EntityType, IndexType
6
4
 
7
5
  from .index import IndexInfo, IndexItem, IndexManagerBase
8
- from .manager import DatabaseManager
9
-
10
- if TYPE_CHECKING:
11
- from . import InfrahubDatabase
12
6
 
13
7
 
14
8
  class IndexNodeMemgraph(IndexItem):
@@ -52,9 +46,3 @@ class IndexManagerMemgraph(IndexManagerBase):
52
46
  )
53
47
 
54
48
  return results
55
-
56
-
57
- class DatabaseManagerMemgraph(DatabaseManager):
58
- def __init__(self, db: InfrahubDatabase) -> None:
59
- super().__init__(db=db)
60
- self.index = IndexManagerMemgraph(db=db)
@@ -1,15 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
4
-
5
3
  from infrahub.constants.database import EntityType, IndexType
6
4
  from infrahub.core.query import QueryType
7
5
 
8
6
  from .index import IndexInfo, IndexItem, IndexManagerBase
9
- from .manager import DatabaseManager
10
-
11
- if TYPE_CHECKING:
12
- from . import InfrahubDatabase
13
7
 
14
8
 
15
9
  class IndexRelNeo4j(IndexItem):
@@ -68,9 +62,3 @@ class IndexManagerNeo4j(IndexManagerBase):
68
62
  )
69
63
 
70
64
  return results
71
-
72
-
73
- class DatabaseManagerNeo4j(DatabaseManager):
74
- def __init__(self, db: InfrahubDatabase) -> None:
75
- super().__init__(db=db)
76
- self.index = IndexManagerNeo4j(db=db)
@@ -71,7 +71,11 @@ class UpdateComputedAttribute(Mutation):
71
71
 
72
72
  if not (
73
73
  target_node := await NodeManager.get_one(
74
- db=graphql_context.db, kind=node_schema.kind, id=str(data.id), branch=graphql_context.branch
74
+ db=graphql_context.db,
75
+ kind=node_schema.kind,
76
+ id=str(data.id),
77
+ branch=graphql_context.branch,
78
+ fields={target_attribute.name: None},
75
79
  )
76
80
  ):
77
81
  raise NodeNotFoundError(
@@ -10,6 +10,7 @@ from infrahub.core import registry
10
10
  from infrahub.core.constants import DiffAction, RelationshipCardinality
11
11
  from infrahub.core.constants.database import DatabaseEdgeType
12
12
  from infrahub.core.diff.model.path import NameTrackingId
13
+ from infrahub.core.diff.query.filters import EnrichedDiffQueryFilters
13
14
  from infrahub.core.diff.repository.repository import DiffRepository
14
15
  from infrahub.core.query.diff import DiffCountChanges
15
16
  from infrahub.core.timestamp import Timestamp
@@ -415,7 +416,7 @@ class DiffTreeResolver:
415
416
  diff_branch_names=[diff_branch.name],
416
417
  from_time=from_timestamp,
417
418
  to_time=to_timestamp,
418
- filters=filters_dict,
419
+ filters=EnrichedDiffQueryFilters(**filters_dict),
419
420
  include_parents=include_parents,
420
421
  limit=limit,
421
422
  offset=offset,
infrahub/server.py CHANGED
@@ -23,7 +23,6 @@ from infrahub import __version__, config
23
23
  from infrahub.api import router as api
24
24
  from infrahub.api.exception_handlers import generic_api_exception_handler
25
25
  from infrahub.components import ComponentType
26
- from infrahub.core.graph.index import attr_value_index, node_indexes, rel_indexes
27
26
  from infrahub.core.initialization import initialization
28
27
  from infrahub.database import InfrahubDatabase, InfrahubDatabaseMode, get_db
29
28
  from infrahub.dependencies.registry import build_component_registry
@@ -58,9 +57,6 @@ async def app_initialization(application: FastAPI, enable_scheduler: bool = True
58
57
 
59
58
  # Initialize database Driver and load local registry
60
59
  database = application.state.db = InfrahubDatabase(mode=InfrahubDatabaseMode.DRIVER, driver=await get_db())
61
- if config.SETTINGS.experimental_features.value_db_index:
62
- node_indexes.append(attr_value_index)
63
- database.manager.index.init(nodes=node_indexes, rels=rel_indexes)
64
60
 
65
61
  build_component_registry()
66
62
 
@@ -85,7 +81,7 @@ async def app_initialization(application: FastAPI, enable_scheduler: bool = True
85
81
  initialize_lock(service=service)
86
82
  # We must initialize DB after initialize lock and initialize lock depends on cache initialization
87
83
  async with application.state.db.start_session() as db:
88
- await initialization(db=db)
84
+ await initialization(db=db, add_database_indexes=True)
89
85
 
90
86
  application.state.service = service
91
87
  application.state.response_delay = config.SETTINGS.miscellaneous.response_delay
@@ -5,8 +5,11 @@ from enum import Enum
5
5
  from typing import TYPE_CHECKING, Any
6
6
 
7
7
  from prefect.events.actions import RunDeployment
8
+ from prefect.events.schemas.automations import (
9
+ Automation, # noqa: TC002
10
+ Posture,
11
+ )
8
12
  from prefect.events.schemas.automations import EventTrigger as PrefectEventTrigger
9
- from prefect.events.schemas.automations import Posture
10
13
  from prefect.events.schemas.events import ResourceSpecification
11
14
  from pydantic import BaseModel, Field
12
15
 
@@ -19,6 +22,13 @@ if TYPE_CHECKING:
19
22
  from uuid import UUID
20
23
 
21
24
 
25
+ class TriggerSetupReport(BaseModel):
26
+ created: list[TriggerDefinition] = Field(default_factory=list)
27
+ updated: list[TriggerDefinition] = Field(default_factory=list)
28
+ deleted: list[Automation] = Field(default_factory=list)
29
+ unchanged: list[TriggerDefinition] = Field(default_factory=list)
30
+
31
+
22
32
  class TriggerType(str, Enum):
23
33
  BUILTIN = "builtin"
24
34
  WEBHOOK = "webhook"