infrahub-server 1.4.9__py3-none-any.whl → 1.5.0b0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. infrahub/actions/tasks.py +200 -16
  2. infrahub/api/artifact.py +3 -0
  3. infrahub/api/query.py +2 -0
  4. infrahub/api/schema.py +3 -0
  5. infrahub/auth.py +5 -5
  6. infrahub/cli/db.py +2 -2
  7. infrahub/config.py +7 -2
  8. infrahub/core/attribute.py +22 -19
  9. infrahub/core/branch/models.py +2 -2
  10. infrahub/core/branch/needs_rebase_status.py +11 -0
  11. infrahub/core/branch/tasks.py +2 -2
  12. infrahub/core/constants/__init__.py +1 -0
  13. infrahub/core/convert_object_type/object_conversion.py +201 -0
  14. infrahub/core/convert_object_type/repository_conversion.py +89 -0
  15. infrahub/core/convert_object_type/schema_mapping.py +27 -3
  16. infrahub/core/diff/query/artifact.py +12 -9
  17. infrahub/core/graph/__init__.py +1 -1
  18. infrahub/core/initialization.py +2 -2
  19. infrahub/core/manager.py +3 -81
  20. infrahub/core/migrations/graph/__init__.py +2 -0
  21. infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +166 -0
  22. infrahub/core/node/__init__.py +26 -3
  23. infrahub/core/node/create.py +79 -38
  24. infrahub/core/node/lock_utils.py +98 -0
  25. infrahub/core/property.py +11 -0
  26. infrahub/core/protocols.py +1 -0
  27. infrahub/core/query/attribute.py +27 -15
  28. infrahub/core/query/node.py +47 -184
  29. infrahub/core/query/relationship.py +43 -26
  30. infrahub/core/query/subquery.py +0 -8
  31. infrahub/core/relationship/model.py +59 -19
  32. infrahub/core/schema/attribute_schema.py +0 -2
  33. infrahub/core/schema/definitions/core/repository.py +7 -0
  34. infrahub/core/schema/relationship_schema.py +0 -1
  35. infrahub/core/schema/schema_branch.py +3 -2
  36. infrahub/generators/models.py +31 -12
  37. infrahub/generators/tasks.py +3 -1
  38. infrahub/git/base.py +38 -1
  39. infrahub/graphql/api/dependencies.py +2 -4
  40. infrahub/graphql/api/endpoints.py +2 -2
  41. infrahub/graphql/app.py +2 -4
  42. infrahub/graphql/initialization.py +2 -3
  43. infrahub/graphql/manager.py +212 -137
  44. infrahub/graphql/middleware.py +12 -0
  45. infrahub/graphql/mutations/branch.py +11 -0
  46. infrahub/graphql/mutations/computed_attribute.py +110 -3
  47. infrahub/graphql/mutations/convert_object_type.py +34 -13
  48. infrahub/graphql/mutations/ipam.py +21 -8
  49. infrahub/graphql/mutations/main.py +37 -153
  50. infrahub/graphql/mutations/profile.py +195 -0
  51. infrahub/graphql/mutations/proposed_change.py +2 -1
  52. infrahub/graphql/mutations/repository.py +22 -83
  53. infrahub/graphql/mutations/webhook.py +1 -1
  54. infrahub/graphql/registry.py +173 -0
  55. infrahub/graphql/schema.py +4 -1
  56. infrahub/lock.py +52 -26
  57. infrahub/locks/__init__.py +0 -0
  58. infrahub/locks/tasks.py +37 -0
  59. infrahub/patch/plan_writer.py +2 -2
  60. infrahub/profiles/__init__.py +0 -0
  61. infrahub/profiles/node_applier.py +101 -0
  62. infrahub/profiles/queries/__init__.py +0 -0
  63. infrahub/profiles/queries/get_profile_data.py +99 -0
  64. infrahub/profiles/tasks.py +63 -0
  65. infrahub/repositories/__init__.py +0 -0
  66. infrahub/repositories/create_repository.py +113 -0
  67. infrahub/tasks/registry.py +6 -4
  68. infrahub/webhook/models.py +1 -1
  69. infrahub/workflows/catalogue.py +38 -3
  70. infrahub/workflows/models.py +17 -2
  71. infrahub_sdk/branch.py +5 -8
  72. infrahub_sdk/client.py +364 -84
  73. infrahub_sdk/convert_object_type.py +61 -0
  74. infrahub_sdk/ctl/check.py +2 -3
  75. infrahub_sdk/ctl/cli_commands.py +16 -12
  76. infrahub_sdk/ctl/config.py +8 -2
  77. infrahub_sdk/ctl/generator.py +2 -3
  78. infrahub_sdk/ctl/repository.py +39 -1
  79. infrahub_sdk/ctl/schema.py +12 -1
  80. infrahub_sdk/ctl/utils.py +4 -0
  81. infrahub_sdk/ctl/validate.py +5 -3
  82. infrahub_sdk/diff.py +4 -5
  83. infrahub_sdk/exceptions.py +2 -0
  84. infrahub_sdk/graphql.py +7 -2
  85. infrahub_sdk/node/attribute.py +2 -0
  86. infrahub_sdk/node/node.py +28 -20
  87. infrahub_sdk/playback.py +1 -2
  88. infrahub_sdk/protocols.py +40 -6
  89. infrahub_sdk/pytest_plugin/plugin.py +7 -4
  90. infrahub_sdk/pytest_plugin/utils.py +40 -0
  91. infrahub_sdk/repository.py +1 -2
  92. infrahub_sdk/schema/main.py +1 -0
  93. infrahub_sdk/spec/object.py +43 -4
  94. infrahub_sdk/spec/range_expansion.py +118 -0
  95. infrahub_sdk/timestamp.py +18 -6
  96. {infrahub_server-1.4.9.dist-info → infrahub_server-1.5.0b0.dist-info}/METADATA +20 -24
  97. {infrahub_server-1.4.9.dist-info → infrahub_server-1.5.0b0.dist-info}/RECORD +102 -84
  98. infrahub_testcontainers/models.py +2 -2
  99. infrahub_testcontainers/performance_test.py +4 -4
  100. infrahub/core/convert_object_type/conversion.py +0 -134
  101. {infrahub_server-1.4.9.dist-info → infrahub_server-1.5.0b0.dist-info}/LICENSE.txt +0 -0
  102. {infrahub_server-1.4.9.dist-info → infrahub_server-1.5.0b0.dist-info}/WHEEL +0 -0
  103. {infrahub_server-1.4.9.dist-info → infrahub_server-1.5.0b0.dist-info}/entry_points.txt +0 -0
@@ -11,6 +11,7 @@ from infrahub import config
11
11
  from infrahub.core import registry
12
12
  from infrahub.core.constants import (
13
13
  GLOBAL_BRANCH_NAME,
14
+ PROFILE_NODE_RELATIONSHIP_IDENTIFIER,
14
15
  AttributeDBNodeType,
15
16
  RelationshipDirection,
16
17
  RelationshipHierarchyDirection,
@@ -43,7 +44,6 @@ class NodeToProcess:
43
44
 
44
45
  node_id: str
45
46
  node_uuid: str
46
- profile_uuids: list[str]
47
47
 
48
48
  updated_at: str
49
49
 
@@ -606,6 +606,7 @@ class NodeListGetAttributeQuery(Query):
606
606
 
607
607
  async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG002
608
608
  self.params["ids"] = self.ids
609
+ self.params["profile_relationship_name"] = PROFILE_NODE_RELATIONSHIP_IDENTIFIER
609
610
 
610
611
  branch_filter, branch_params = self.branch.get_query_filter_path(
611
612
  at=self.at, branch_agnostic=self.branch_agnostic
@@ -614,6 +615,7 @@ class NodeListGetAttributeQuery(Query):
614
615
 
615
616
  query = """
616
617
  MATCH (n:Node) WHERE n.uuid IN $ids
618
+ WITH n, exists((n)-[:IS_RELATED]-(:Relationship {name: $profile_relationship_name})) AS might_use_profile
617
619
  MATCH (n)-[:HAS_ATTRIBUTE]-(a:Attribute)
618
620
  """
619
621
  if self.fields:
@@ -626,15 +628,23 @@ class NodeListGetAttributeQuery(Query):
626
628
  CALL (n, a) {
627
629
  MATCH (n)-[r:HAS_ATTRIBUTE]-(a:Attribute)
628
630
  WHERE %(branch_filter)s
629
- RETURN n as n1, r as r1, a as a1
631
+ RETURN r AS r1
630
632
  ORDER BY r.branch_level DESC, r.from DESC
631
633
  LIMIT 1
632
634
  }
633
- WITH n1 as n, r1, a1 as a
635
+ WITH n, r1, a, might_use_profile
634
636
  WHERE r1.status = "active"
635
- WITH n, r1, a
637
+ WITH n, r1, a, might_use_profile
636
638
  MATCH (a)-[r:HAS_VALUE]-(av:AttributeValue)
637
639
  WHERE %(branch_filter)s
640
+ CALL (a, might_use_profile) {
641
+ OPTIONAL MATCH (a)-[r:HAS_SOURCE]->(:CoreProfile)
642
+ WHERE might_use_profile = TRUE AND %(branch_filter)s
643
+ RETURN r.status = "active" AS has_active_profile
644
+ ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
645
+ LIMIT 1
646
+ }
647
+ WITH *, has_active_profile = TRUE AS is_from_profile
638
648
  CALL (a, av) {
639
649
  MATCH (a)-[r:HAS_VALUE]-(av:AttributeValue)
640
650
  WHERE %(branch_filter)s
@@ -642,13 +652,13 @@ class NodeListGetAttributeQuery(Query):
642
652
  ORDER BY r.branch_level DESC, r.from DESC
643
653
  LIMIT 1
644
654
  }
645
- WITH n, r1, a1 as a, r2, av1 as av
655
+ WITH n, r1, a1 as a, r2, av1 as av, is_from_profile
646
656
  WHERE r2.status = "active"
647
- WITH n, a, av, r1, r2
657
+ WITH n, a, av, r1, r2, is_from_profile
648
658
  """ % {"branch_filter": branch_filter}
649
659
  self.add_to_query(query)
650
660
 
651
- self.return_labels = ["n", "a", "av", "r1", "r2"]
661
+ self.return_labels = ["n", "a", "av", "r1", "r2", "is_from_profile"]
652
662
 
653
663
  # Add Is_Protected and Is_visible
654
664
  rel_isv_branch_filter, _ = self.branch.get_query_filter_path(
@@ -668,16 +678,32 @@ class NodeListGetAttributeQuery(Query):
668
678
 
669
679
  if self.include_source:
670
680
  query = """
671
- OPTIONAL MATCH (a)-[rel_source:HAS_SOURCE]-(source)
672
- WHERE all(r IN [rel_source] WHERE ( %(branch_filter)s ))
681
+ CALL (a) {
682
+ OPTIONAL MATCH (a)-[rel_source:HAS_SOURCE]-(source)
683
+ WHERE all(r IN [rel_source] WHERE ( %(branch_filter)s ))
684
+ RETURN source, rel_source
685
+ ORDER BY rel_source.branch_level DESC, rel_source.from DESC, rel_source.status ASC
686
+ LIMIT 1
687
+ }
688
+ WITH *,
689
+ CASE WHEN rel_source.status = "active" THEN source ELSE NULL END AS source,
690
+ CASE WHEN rel_source.status = "active" THEN rel_source ELSE NULL END AS rel_source
673
691
  """ % {"branch_filter": branch_filter}
674
692
  self.add_to_query(query)
675
693
  self.return_labels.extend(["source", "rel_source"])
676
694
 
677
695
  if self.include_owner:
678
696
  query = """
679
- OPTIONAL MATCH (a)-[rel_owner:HAS_OWNER]-(owner)
680
- WHERE all(r IN [rel_owner] WHERE ( %(branch_filter)s ))
697
+ CALL (a) {
698
+ OPTIONAL MATCH (a)-[rel_owner:HAS_OWNER]-(owner)
699
+ WHERE all(r IN [rel_owner] WHERE ( %(branch_filter)s ))
700
+ RETURN owner, rel_owner
701
+ ORDER BY rel_owner.branch_level DESC, rel_owner.from DESC, rel_owner.status ASC
702
+ LIMIT 1
703
+ }
704
+ WITH *,
705
+ CASE WHEN rel_owner.status = "active" THEN owner ELSE NULL END AS owner,
706
+ CASE WHEN rel_owner.status = "active" THEN rel_owner ELSE NULL END AS rel_owner
681
707
  """ % {"branch_filter": branch_filter}
682
708
  self.add_to_query(query)
683
709
  self.return_labels.extend(["owner", "rel_owner"])
@@ -708,6 +734,7 @@ class NodeListGetAttributeQuery(Query):
708
734
  def _extract_attribute_data(self, result: QueryResult) -> AttributeFromDB:
709
735
  attr = result.get_node("a")
710
736
  attr_value = result.get_node("av")
737
+ is_from_profile = result.get_as_type(label="is_from_profile", return_type=bool)
711
738
 
712
739
  data = AttributeFromDB(
713
740
  name=attr.get("name"),
@@ -719,6 +746,7 @@ class NodeListGetAttributeQuery(Query):
719
746
  updated_at=result.get_rel("r2").get("from"),
720
747
  value=attr_value.get("value"),
721
748
  is_default=attr_value.get("is_default"),
749
+ is_from_profile=is_from_profile,
722
750
  content=attr_value._properties,
723
751
  branch=self.branch.name,
724
752
  flag_properties={
@@ -942,6 +970,7 @@ class NodeListGetInfoQuery(Query):
942
970
  at=self.at, branch_agnostic=self.branch_agnostic
943
971
  )
944
972
  self.params.update(branch_params)
973
+ self.params["ids"] = self.ids
945
974
 
946
975
  query = """
947
976
  MATCH p = (root:Root)<-[:IS_PART_OF]-(n:Node)
@@ -955,16 +984,11 @@ class NodeListGetInfoQuery(Query):
955
984
  }
956
985
  WITH n1 as n, r1 as rb
957
986
  WHERE rb.status = "active"
958
- OPTIONAL MATCH profile_path = (n)-[:IS_RELATED]->(profile_r:Relationship)<-[:IS_RELATED]-(profile:Node)-[:IS_PART_OF]->(:Root)
959
- WHERE profile_r.name = "node__profile"
960
- AND profile.namespace = "Profile"
961
- AND all(r in relationships(profile_path) WHERE %(branch_filter)s and r.status = "active")
962
987
  """ % {"branch_filter": branch_filter}
963
988
 
964
989
  self.add_to_query(query)
965
- self.params["ids"] = self.ids
966
990
 
967
- self.return_labels = ["collect(profile.uuid) as profile_uuids", "n", "rb"]
991
+ self.return_labels = ["n", "rb"]
968
992
 
969
993
  async def get_nodes(self, db: InfrahubDatabase, duplicate: bool = False) -> AsyncIterator[NodeToProcess]:
970
994
  """Return all the node objects as NodeToProcess."""
@@ -978,24 +1002,11 @@ class NodeListGetInfoQuery(Query):
978
1002
  schema=schema,
979
1003
  node_id=result.get_node("n").element_id,
980
1004
  node_uuid=result.get_node("n").get("uuid"),
981
- profile_uuids=[str(puuid) for puuid in result.get("profile_uuids")],
982
1005
  updated_at=result.get_rel("rb").get("from"),
983
1006
  branch=node_branch,
984
1007
  labels=list(result.get_node("n").labels),
985
1008
  )
986
1009
 
987
- def get_profile_ids_by_node_id(self) -> dict[str, list[str]]:
988
- profile_id_map: dict[str, list[str]] = {}
989
- for result in self.results:
990
- node_id = result.get_node("n").get("uuid")
991
- profile_ids = result.get("profile_uuids")
992
- if not node_id or not profile_ids:
993
- continue
994
- if node_id not in profile_id_map:
995
- profile_id_map[node_id] = []
996
- profile_id_map[node_id].extend(profile_ids)
997
- return profile_id_map
998
-
999
1010
 
1000
1011
  class FieldAttributeRequirementType(Enum):
1001
1012
  FILTER = "filter"
@@ -1012,7 +1023,7 @@ class FieldAttributeRequirement:
1012
1023
  types: list[FieldAttributeRequirementType] = dataclass_field(default_factory=list)
1013
1024
 
1014
1025
  @property
1015
- def supports_profile(self) -> bool:
1026
+ def is_attribute_value(self) -> bool:
1016
1027
  return bool(self.field and self.field.is_attribute and self.field_attr_name in ("value", "values", "isnull"))
1017
1028
 
1018
1029
  @property
@@ -1023,26 +1034,10 @@ class FieldAttributeRequirement:
1023
1034
  def is_order(self) -> bool:
1024
1035
  return FieldAttributeRequirementType.ORDER in self.types
1025
1036
 
1026
- @property
1027
- def is_default_query_variable(self) -> str:
1028
- return f"attr{self.index}_is_default"
1029
-
1030
1037
  @property
1031
1038
  def node_value_query_variable(self) -> str:
1032
1039
  return f"attr{self.index}_node_value"
1033
1040
 
1034
- @property
1035
- def profile_value_query_variable(self) -> str:
1036
- return f"attr{self.index}_profile_value"
1037
-
1038
- @property
1039
- def profile_final_value_query_variable(self) -> str:
1040
- return f"attr{self.index}_final_profile_value"
1041
-
1042
- @property
1043
- def final_value_query_variable(self) -> str:
1044
- return f"attr{self.index}_final_value"
1045
-
1046
1041
  @property
1047
1042
  def comparison_operator(self) -> str:
1048
1043
  if self.field_attr_name == "isnull":
@@ -1178,7 +1173,6 @@ class NodeGetListQuery(Query):
1178
1173
  self.params["node_ids"] = self.filters["ids"]
1179
1174
 
1180
1175
  field_attribute_requirements = self._get_field_requirements(disable_order=disable_order)
1181
- use_profiles = any(far for far in field_attribute_requirements if far.supports_profile)
1182
1176
  await self._add_node_filter_attributes(
1183
1177
  db=db, field_attribute_requirements=field_attribute_requirements, branch_filter=branch_filter
1184
1178
  )
@@ -1190,21 +1184,11 @@ class NodeGetListQuery(Query):
1190
1184
  for far in field_attribute_requirements:
1191
1185
  if not far.is_order:
1192
1186
  continue
1193
- if far.supports_profile:
1194
- self.order_by.append(far.final_value_query_variable)
1195
- continue
1196
1187
  self.order_by.append(far.node_value_query_variable)
1197
1188
 
1198
1189
  # Always order by uuid to guarantee pagination, see https://github.com/opsmill/infrahub/pull/4704.
1199
1190
  self.order_by.append("n.uuid")
1200
1191
 
1201
- if use_profiles:
1202
- await self._add_profiles_per_node_query(db=db, branch_filter=branch_filter)
1203
- await self._add_profile_attributes(
1204
- db=db, field_attribute_requirements=field_attribute_requirements, branch_filter=branch_filter
1205
- )
1206
- await self._add_profile_rollups(field_attribute_requirements=field_attribute_requirements)
1207
-
1208
1192
  self._add_final_filter(field_attribute_requirements=field_attribute_requirements)
1209
1193
 
1210
1194
  async def _add_node_filter_attributes(
@@ -1222,8 +1206,6 @@ class NodeGetListQuery(Query):
1222
1206
 
1223
1207
  for far in field_attribute_requirements:
1224
1208
  extra_tail_properties = {far.node_value_query_variable: "value"}
1225
- if far.supports_profile:
1226
- extra_tail_properties[far.is_default_query_variable] = "is_default"
1227
1209
  subquery, subquery_params, subquery_result_name = await build_subquery_filter(
1228
1210
  db=db,
1229
1211
  field=far.field,
@@ -1234,7 +1216,6 @@ class NodeGetListQuery(Query):
1234
1216
  branch=self.branch,
1235
1217
  subquery_idx=far.index,
1236
1218
  partial_match=self.partial_match,
1237
- support_profiles=far.supports_profile,
1238
1219
  extra_tail_properties=extra_tail_properties,
1239
1220
  )
1240
1221
  for query_var in extra_tail_properties:
@@ -1274,9 +1255,6 @@ class NodeGetListQuery(Query):
1274
1255
  for far in field_attribute_requirements:
1275
1256
  if far.field is None:
1276
1257
  continue
1277
- extra_tail_properties = {}
1278
- if far.supports_profile:
1279
- extra_tail_properties[far.is_default_query_variable] = "is_default"
1280
1258
 
1281
1259
  subquery, subquery_params, _ = await build_subquery_order(
1282
1260
  db=db,
@@ -1287,11 +1265,7 @@ class NodeGetListQuery(Query):
1287
1265
  branch=self.branch,
1288
1266
  subquery_idx=far.index,
1289
1267
  result_prefix=far.node_value_query_variable,
1290
- support_profiles=far.supports_profile,
1291
- extra_tail_properties=extra_tail_properties,
1292
1268
  )
1293
- for query_var in extra_tail_properties:
1294
- self._track_variable(query_var)
1295
1269
  self._track_variable(far.node_value_query_variable)
1296
1270
  with_str = ", ".join(self._get_tracked_variables())
1297
1271
 
@@ -1305,122 +1279,11 @@ class NodeGetListQuery(Query):
1305
1279
  self.add_to_query(sort_query)
1306
1280
  self.params.update(sort_params)
1307
1281
 
1308
- async def _add_profiles_per_node_query(self, db: InfrahubDatabase, branch_filter: str) -> None:
1309
- with_str = ", ".join(self._get_tracked_variables())
1310
- froms_str = db.render_list_comprehension(items="relationships(profile_path)", item_name="from")
1311
- profiles_per_node_query = (
1312
- """
1313
- CALL (n) {
1314
- OPTIONAL MATCH profile_path = (n)-[:IS_RELATED]->(profile_r:Relationship)<-[:IS_RELATED]-(maybe_profile_n:Node)-[:IS_PART_OF]->(:Root)
1315
- WHERE profile_r.name = "node__profile"
1316
- AND all(r in relationships(profile_path) WHERE %(branch_filter)s)
1317
- WITH
1318
- maybe_profile_n,
1319
- profile_path,
1320
- reduce(br_lvl = 0, r in relationships(profile_path) | br_lvl + r.branch_level) AS branch_level,
1321
- %(froms_str)s AS froms,
1322
- all(r in relationships(profile_path) WHERE r.status = "active") AS is_active
1323
- RETURN maybe_profile_n, is_active, branch_level, froms
1324
- }
1325
- WITH %(with_str)s, maybe_profile_n, branch_level, froms, is_active
1326
- ORDER BY n.uuid, maybe_profile_n.uuid, branch_level DESC, froms[-1] DESC, froms[-2] DESC, froms[-3] DESC
1327
- WITH %(with_str)s, maybe_profile_n, collect(is_active) as ordered_is_actives
1328
- WITH %(with_str)s, CASE
1329
- WHEN ordered_is_actives[0] = True THEN maybe_profile_n ELSE NULL
1330
- END AS profile_n
1331
- CALL (profile_n) {
1332
- OPTIONAL MATCH profile_priority_path = (profile_n)-[pr1:HAS_ATTRIBUTE]->(a:Attribute)-[pr2:HAS_VALUE]->(av:AttributeValue)
1333
- WHERE a.name = "profile_priority"
1334
- AND all(r in relationships(profile_priority_path) WHERE %(branch_filter)s and r.status = "active")
1335
- RETURN av.value as profile_priority
1336
- ORDER BY pr1.branch_level + pr2.branch_level DESC, pr2.from DESC, pr1.from DESC
1337
- LIMIT 1
1338
- }
1339
- WITH %(with_str)s, profile_n, profile_priority
1340
- """
1341
- ) % {"branch_filter": branch_filter, "with_str": with_str, "froms_str": froms_str}
1342
- self.add_to_query(profiles_per_node_query)
1343
- self._track_variable("profile_n")
1344
- self._track_variable("profile_priority")
1345
-
1346
- async def _add_profile_attributes(
1347
- self, db: InfrahubDatabase, field_attribute_requirements: list[FieldAttributeRequirement], branch_filter: str
1348
- ) -> None:
1349
- attributes_queries: list[str] = []
1350
- attributes_params: dict[str, Any] = {}
1351
- profile_attributes = [far for far in field_attribute_requirements if far.supports_profile]
1352
-
1353
- for profile_attr in profile_attributes:
1354
- if not profile_attr.field:
1355
- continue
1356
- subquery, subquery_params, _ = await build_subquery_order(
1357
- db=db,
1358
- field=profile_attr.field,
1359
- node_alias="profile_n",
1360
- name=profile_attr.field_name,
1361
- order_by=profile_attr.field_attr_name,
1362
- branch_filter=branch_filter,
1363
- branch=self.branch,
1364
- subquery_idx=profile_attr.index,
1365
- result_prefix=profile_attr.profile_value_query_variable,
1366
- support_profiles=False,
1367
- )
1368
- attributes_params.update(subquery_params)
1369
- self._track_variable(profile_attr.profile_value_query_variable)
1370
- with_str = ", ".join(self._get_tracked_variables())
1371
-
1372
- attributes_queries.append("CALL (profile_n) {")
1373
- attributes_queries.append(subquery)
1374
- attributes_queries.append("}")
1375
- attributes_queries.append(f"WITH {with_str}")
1376
-
1377
- self.add_to_query(attributes_queries)
1378
- self.params.update(attributes_params)
1379
-
1380
- async def _add_profile_rollups(self, field_attribute_requirements: list[FieldAttributeRequirement]) -> None:
1381
- profile_attributes = [far for far in field_attribute_requirements if far.supports_profile]
1382
- profile_value_collects = []
1383
- for profile_attr in profile_attributes:
1384
- self._untrack_variable(profile_attr.profile_value_query_variable)
1385
- profile_value_collects.append(
1386
- f"""head(
1387
- reduce(
1388
- non_null_values = [], v in collect({profile_attr.profile_value_query_variable}) |
1389
- CASE WHEN v IS NOT NULL AND v <> "NULL" THEN non_null_values + [v] ELSE non_null_values END
1390
- )
1391
- ) as {profile_attr.profile_final_value_query_variable}"""
1392
- )
1393
- self._untrack_variable("profile_n")
1394
- self._untrack_variable("profile_priority")
1395
- profile_rollup_with_str = ", ".join(self._get_tracked_variables() + profile_value_collects)
1396
- profile_rollup_query = f"""
1397
- ORDER BY n.uuid, profile_priority ASC, profile_n.uuid ASC
1398
- WITH {profile_rollup_with_str}
1399
- """
1400
- self.add_to_query(profile_rollup_query)
1401
- for profile_attr in profile_attributes:
1402
- self._track_variable(profile_attr.profile_final_value_query_variable)
1403
-
1404
- final_value_with = []
1405
- for profile_attr in profile_attributes:
1406
- final_value_with.append(f"""
1407
- CASE
1408
- WHEN {profile_attr.is_default_query_variable} AND {profile_attr.profile_final_value_query_variable} IS NOT NULL
1409
- THEN {profile_attr.profile_final_value_query_variable}
1410
- ELSE {profile_attr.node_value_query_variable}
1411
- END AS {profile_attr.final_value_query_variable}
1412
- """)
1413
- self._untrack_variable(profile_attr.is_default_query_variable)
1414
- self._untrack_variable(profile_attr.profile_final_value_query_variable)
1415
- self._untrack_variable(profile_attr.node_value_query_variable)
1416
- final_value_with_str = ", ".join(self._get_tracked_variables() + final_value_with)
1417
- self.add_to_query(f"WITH {final_value_with_str}")
1418
-
1419
1282
  def _add_final_filter(self, field_attribute_requirements: list[FieldAttributeRequirement]) -> None:
1420
1283
  where_parts = []
1421
1284
  where_str = ""
1422
1285
  for far in field_attribute_requirements:
1423
- if not far.is_filter or not far.supports_profile:
1286
+ if not far.is_filter or not far.is_attribute_value:
1424
1287
  continue
1425
1288
  var_name = f"final_attr_value{far.index}"
1426
1289
  self.params[var_name] = far.field_attr_comparison_value
@@ -1429,11 +1292,11 @@ class NodeGetListQuery(Query):
1429
1292
  # If the any filter is an array/list
1430
1293
  var_array = f"{var_name}_array"
1431
1294
  where_parts.append(
1432
- f"any({var_array} IN ${var_name} WHERE toLower(toString({far.final_value_query_variable})) CONTAINS toLower({var_array}))"
1295
+ f"any({var_array} IN ${var_name} WHERE toLower(toString({far.node_value_query_variable})) CONTAINS toLower({var_array}))"
1433
1296
  )
1434
1297
  else:
1435
1298
  where_parts.append(
1436
- f"toLower(toString({far.final_value_query_variable})) CONTAINS toLower(toString(${var_name}))"
1299
+ f"toLower(toString({far.node_value_query_variable})) CONTAINS toLower(toString(${var_name}))"
1437
1300
  )
1438
1301
  continue
1439
1302
  if far.field and isinstance(far.field, AttributeSchema) and far.field.kind == "List":
@@ -1442,10 +1305,10 @@ class NodeGetListQuery(Query):
1442
1305
  else:
1443
1306
  self.params[var_name] = build_regex_attrs(values=[far.field_attr_comparison_value])
1444
1307
 
1445
- where_parts.append(f"toString({far.final_value_query_variable}) =~ ${var_name}")
1308
+ where_parts.append(f"toString({far.node_value_query_variable}) =~ ${var_name}")
1446
1309
  continue
1447
1310
 
1448
- where_parts.append(f"{far.final_value_query_variable} {far.comparison_operator} ${var_name}")
1311
+ where_parts.append(f"{far.node_value_query_variable} {far.comparison_operator} ${var_name}")
1449
1312
  if where_parts:
1450
1313
  where_str = "WHERE " + " AND ".join(where_parts)
1451
1314
  self.add_to_query(where_str)
@@ -350,17 +350,20 @@ class RelationshipUpdatePropertyQuery(RelationshipQuery):
350
350
 
351
351
  def __init__(
352
352
  self,
353
- properties_to_update: list[str],
354
- data: RelationshipPeerData,
353
+ rel_node_id: str,
354
+ flag_properties_to_update: dict[str, bool],
355
+ node_properties_to_update: dict[str, str],
355
356
  **kwargs,
356
357
  ):
357
- self.properties_to_update = properties_to_update
358
- self.data = data
359
-
358
+ self.rel_node_id = rel_node_id
359
+ if not flag_properties_to_update and not node_properties_to_update:
360
+ raise ValueError("Either flag_properties_to_update or node_properties_to_update must be set")
361
+ self.flag_properties_to_update = flag_properties_to_update
362
+ self.node_properties_to_update = node_properties_to_update
360
363
  super().__init__(**kwargs)
361
364
 
362
365
  async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG002
363
- self.params["rel_node_id"] = self.data.rel_node_id
366
+ self.params["rel_node_id"] = self.rel_node_id
364
367
  self.params["branch"] = self.branch.name
365
368
  self.params["branch_level"] = self.branch.hierarchy_level
366
369
  self.params["at"] = self.at.to_string()
@@ -370,36 +373,51 @@ class RelationshipUpdatePropertyQuery(RelationshipQuery):
370
373
  """
371
374
  self.add_to_query(query)
372
375
 
373
- self.query_add_all_flag_property_merge()
374
376
  self.query_add_all_node_property_merge()
377
+ self.query_add_all_flag_property_merge()
375
378
 
376
- self.query_add_all_flag_property_create()
377
379
  self.query_add_all_node_property_create()
380
+ self.query_add_all_flag_property_create()
378
381
 
379
382
  def query_add_all_flag_property_merge(self) -> None:
380
- for prop_name in self.rel._flag_properties:
381
- if prop_name in self.properties_to_update:
382
- self.query_add_flag_property_merge(name=prop_name)
383
+ for prop_name, prop_value in self.flag_properties_to_update.items():
384
+ self.query_add_flag_property_merge(name=prop_name, value=prop_value)
383
385
 
384
- def query_add_flag_property_merge(self, name: str) -> None:
386
+ def query_add_flag_property_merge(self, name: str, value: bool) -> None:
385
387
  self.add_to_query("MERGE (prop_%s:Boolean { value: $prop_%s })" % (name, name))
386
- self.params[f"prop_{name}"] = getattr(self.rel, name)
388
+ self.params[f"prop_{name}"] = value
387
389
  self.return_labels.append(f"prop_{name}")
388
390
 
389
391
  def query_add_all_node_property_merge(self) -> None:
390
- for prop_name in self.rel._node_properties:
391
- if prop_name in self.properties_to_update:
392
- self.query_add_node_property_merge(name=prop_name)
392
+ branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at)
393
+ self.params.update(branch_params)
393
394
 
394
- def query_add_node_property_merge(self, name: str) -> None:
395
- self.add_to_query("MERGE (prop_%s:Node { uuid: $prop_%s })" % (name, name))
396
- self.params[f"prop_{name}"] = getattr(self.rel, f"{name}_id")
397
- self.return_labels.append(f"prop_{name}")
395
+ for prop_name, prop_value in self.node_properties_to_update.items():
396
+ self.params[f"prop_{prop_name}"] = prop_value
397
+ if self.branch.is_default or self.branch.is_global:
398
+ node_query = """
399
+ MATCH (prop_%(prop_name)s:Node {uuid: $prop_%(prop_name)s })-[r_%(prop_name)s:IS_PART_OF]->(:Root)
400
+ WHERE r_%(prop_name)s.branch IN $branch0
401
+ AND r_%(prop_name)s.status = "active"
402
+ AND r_%(prop_name)s.from <= $at AND (r_%(prop_name)s.to IS NULL OR r_%(prop_name)s.to > $at)
403
+ WITH *
404
+ LIMIT 1
405
+ """ % {"prop_name": prop_name}
406
+ else:
407
+ node_query = """
408
+ MATCH (prop_%(prop_name)s:Node {uuid: $prop_%(prop_name)s })-[r_%(prop_name)s:IS_PART_OF]->(:Root)
409
+ WHERE all(r in [r_%(prop_name)s] WHERE %(branch_filter)s)
410
+ ORDER BY r_%(prop_name)s.branch_level DESC, r_%(prop_name)s.from DESC, r_%(prop_name)s.status ASC
411
+ LIMIT 1
412
+ WITH *
413
+ WHERE r_%(prop_name)s.status = "active"
414
+ """ % {"branch_filter": branch_filter, "prop_name": prop_name}
415
+ self.add_to_query(node_query)
416
+ self.return_labels.append(f"prop_{prop_name}")
398
417
 
399
418
  def query_add_all_flag_property_create(self) -> None:
400
- for prop_name in self.rel._flag_properties:
401
- if prop_name in self.properties_to_update:
402
- self.query_add_flag_property_create(name=prop_name)
419
+ for prop_name in self.flag_properties_to_update:
420
+ self.query_add_flag_property_create(name=prop_name)
403
421
 
404
422
  def query_add_flag_property_create(self, name: str) -> None:
405
423
  query = """
@@ -411,9 +429,8 @@ class RelationshipUpdatePropertyQuery(RelationshipQuery):
411
429
  self.add_to_query(query)
412
430
 
413
431
  def query_add_all_node_property_create(self) -> None:
414
- for prop_name in self.rel._node_properties:
415
- if prop_name in self.properties_to_update:
416
- self.query_add_node_property_create(name=prop_name)
432
+ for prop_name in self.node_properties_to_update:
433
+ self.query_add_node_property_create(name=prop_name)
417
434
 
418
435
  def query_add_node_property_create(self, name: str) -> None:
419
436
  query = """
@@ -25,12 +25,8 @@ async def build_subquery_filter(
25
25
  partial_match: bool = False,
26
26
  optional_match: bool = False,
27
27
  result_prefix: str = "filter",
28
- support_profiles: bool = False,
29
28
  extra_tail_properties: dict[str, str] | None = None,
30
29
  ) -> tuple[str, dict[str, Any], str]:
31
- support_profiles = (
32
- support_profiles and field and field.is_attribute and filter_name in ("value", "values", "isnull")
33
- )
34
30
  params = {}
35
31
  prefix = f"{result_prefix}{subquery_idx}"
36
32
 
@@ -52,7 +48,6 @@ async def build_subquery_filter(
52
48
  param_prefix=prefix,
53
49
  db=db,
54
50
  partial_match=partial_match,
55
- support_profiles=support_profiles,
56
51
  )
57
52
  params.update(field_params)
58
53
 
@@ -109,10 +104,8 @@ async def build_subquery_order(
109
104
  branch: Branch = None,
110
105
  subquery_idx: int = 1,
111
106
  result_prefix: str | None = None,
112
- support_profiles: bool = False,
113
107
  extra_tail_properties: dict[str, str] | None = None,
114
108
  ) -> tuple[str, dict[str, Any], str]:
115
- support_profiles = support_profiles and field and field.is_attribute and order_by in ("value", "values")
116
109
  params = {}
117
110
  prefix = result_prefix or f"order{subquery_idx}"
118
111
 
@@ -124,7 +117,6 @@ async def build_subquery_order(
124
117
  filter_value=None,
125
118
  branch=branch,
126
119
  param_prefix=prefix,
127
- support_profiles=support_profiles,
128
120
  )
129
121
  params.update(field_params)
130
122