Skip to content

Commit fb6a039

Browse files
committed
fix(backend): improve uniqueness constraint query
This should avoid processing useless rows when we have relationship only constraints. This especially improves the IPAM use-case, where the uniqueness constraint is the (address/prefix, namespace) tuple. Without this change, the query would check for nodes having that same address and fetch ALL nodes present in the same namespace. With this change, the query is now FIRST checking nodes having that same address and THEN checking if these previous nodes are in the same namespace. Signed-off-by: Fatih Acar <[email protected]>
1 parent 699288c commit fb6a039

File tree

1 file changed

+38
-9
lines changed
  • backend/infrahub/core/validators/uniqueness

1 file changed

+38
-9
lines changed

backend/infrahub/core/validators/uniqueness/query.py

+38-9
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def __init__(
3030
def get_context(self) -> dict[str, str]:
3131
return {"kind": self.query_request.kind}
3232

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
3434
branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string(), is_isolated=False)
3535
self.params.update(branch_params)
3636
from_times = db.render_list_comprehension(items="relationships(potential_path)", item_name="from")
@@ -103,48 +103,75 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa
103103
)
104104

105105
attr_paths_subquery = """
106-
MATCH attr_path = (start_node:%(node_kind)s)-[:HAS_ATTRIBUTE]->(attr:Attribute)-[r:HAS_VALUE]->(attr_value:AttributeValue)
106+
MATCH attr_path = (attr_start_node:%(node_kind)s)-[:HAS_ATTRIBUTE]->(attr:Attribute)-[r:HAS_VALUE]->(attr_value:AttributeValue)
107107
WHERE attr.name in $attribute_names
108108
AND ([attr.name, type(r)] in $attr_paths
109109
OR (attr_value.value in $attr_values AND [attr.name, type(r), attr_value.value] in $attr_paths_with_value))
110-
RETURN start_node, attr_path as potential_path, NULL as rel_identifier, attr.name as potential_attr, attr_value.value as potential_attr_value
111110
""" % {"node_kind": self.query_request.kind}
112111

113112
relationship_attr_paths_with_value_subquery = """
114-
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)
113+
OPTIONAL MATCH rel_path = (attr_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)
115114
WHERE relationship_node.name in $relationship_names
116115
AND ([relationship_node.name, rel_attr.name] in $relationship_attr_paths
117116
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))
118-
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-
""" % {"node_kind": self.query_request.kind}
117+
""" % {"node_kind": self.query_request.kind}
120118

121119
relationship_only_attr_paths_subquery = """
122-
MATCH rel_path = (start_node:%(node_kind)s)-[:IS_RELATED]-(relationship_node:Relationship)-[:IS_RELATED]-(related_n:Node)
120+
OPTIONAL MATCH rel_path = (attr_start_node:%(node_kind)s)-[:IS_RELATED]-(relationship_node:Relationship)-[:IS_RELATED]-(related_n:Node)
123121
WHERE %(rel_node_filter)s relationship_node.name in $relationship_only_attr_paths
124-
RETURN start_node, rel_path as potential_path, relationship_node.name as rel_identifier, "id" as potential_attr, related_n.uuid as potential_attr_value
125122
""" % {
126123
"node_kind": self.query_request.kind,
127124
"rel_node_filter": "related_n.uuid IN $relationship_only_attr_values AND "
128125
if relationship_only_attr_values
129126
else "",
130127
}
131128

129+
attr_paths_filter_subquery = """
130+
WITH attr_start_node, attr_path, attr, attr_value
131+
RETURN attr_start_node AS start_node, attr_path AS potential_path, NULL as rel_identifier, attr.name as potential_attr, attr_value.value as potential_attr_value
132+
"""
133+
134+
relationship_attr_paths_with_value_filter_subquery = """
135+
WITH attr_start_node, rel_path, relationship_node, related_n, rel_attr, rel_attr_value
136+
RETURN attr_start_node AS 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
137+
"""
138+
139+
relationship_only_attr_paths_filter_subquery = """
140+
WITH attr_start_node, rel_path, relationship_node, related_n
141+
RETURN attr_start_node AS start_node, rel_path as potential_path, relationship_node.name as rel_identifier, "id" as potential_attr, related_n.uuid as potential_attr_value
142+
"""
143+
132144
select_subqueries = []
145+
filter_subqueries = []
146+
returned_attributes = ["attr_start_node"]
133147
if attr_paths or attr_paths_with_value:
134148
select_subqueries.append(attr_paths_subquery)
149+
filter_subqueries.append(attr_paths_filter_subquery)
150+
returned_attributes.extend(["attr_path", "attr", "attr_value"])
135151
if relationship_attr_paths_with_value or relationship_attr_paths:
136152
select_subqueries.append(relationship_attr_paths_with_value_subquery)
153+
filter_subqueries.append(relationship_attr_paths_with_value_filter_subquery)
154+
returned_attributes.extend(["rel_path", "relationship_node", "related_n", "rel_attr", "rel_attr_value"])
137155
if relationship_only_attr_paths:
138156
select_subqueries.append(relationship_only_attr_paths_subquery)
157+
filter_subqueries.append(relationship_only_attr_paths_filter_subquery)
158+
returned_attributes.extend(["rel_path", "relationship_node", "related_n"])
139159

140-
select_subqueries_str = "UNION".join(select_subqueries)
160+
select_subqueries_str = "".join(select_subqueries)
161+
return_subqueries_str = ", ".join(returned_attributes)
162+
filter_subqueries_str = "UNION".join(filter_subqueries)
141163

142164
# ruff: noqa: E501
143165
query = """
144166
// get attributes for node and its relationships
145167
CALL {
146168
%(select_subqueries_str)s
169+
RETURN %(return_subqueries_str)s
170+
}
171+
CALL {
172+
%(filter_subqueries_str)s
147173
}
174+
WITH start_node, potential_path, rel_identifier, potential_attr, potential_attr_value
148175
CALL {
149176
WITH potential_path
150177
WITH potential_path // workaround for neo4j not allowing WHERE in a WITH of a subquery
@@ -211,6 +238,8 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa
211238
relationship_identifier
212239
""" % {
213240
"select_subqueries_str": select_subqueries_str,
241+
"return_subqueries_str": return_subqueries_str,
242+
"filter_subqueries_str": filter_subqueries_str,
214243
"branch_filter": branch_filter,
215244
"from_times": from_times,
216245
"branch_name_and_level": branch_name_and_level,

0 commit comments

Comments
 (0)