Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/11506.fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Honor `AND`/`OR`/`NOT` clauses in `myDeployments` and `projectDeployments` GraphQL filters, which were previously ignored and caused multi-condition deployment queries to return unfiltered results.
236 changes: 66 additions & 170 deletions src/ai/backend/manager/api/adapters/deployment/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@
QueryCondition,
QueryOrder,
Updater,
combine_conditions_and,
combine_conditions_or,
negate_conditions,
)
Expand Down Expand Up @@ -597,63 +598,7 @@ async def my_search(
raise RuntimeError("No authenticated user in context")
conditions: list[QueryCondition] = []
if input.filter:
f = input.filter
if f.name is not None:
condition = self.convert_string_filter(
f.name,
contains_factory=DeploymentConditions.by_name_contains,
equals_factory=DeploymentConditions.by_name_equals,
starts_with_factory=DeploymentConditions.by_name_starts_with,
ends_with_factory=DeploymentConditions.by_name_ends_with,
in_factory=DeploymentConditions.by_name_in,
)
if condition is not None:
conditions.append(condition)
if f.status is not None:
if f.status.equals is not None:
lifecycles = _status_to_lifecycles(ModelDeploymentStatus(f.status.equals))
if lifecycles:
conditions.append(DeploymentConditions.by_status_in(lifecycles))
if f.status.in_ is not None:
lifecycles = _statuses_to_lifecycles([
ModelDeploymentStatus(s) for s in f.status.in_
])
if lifecycles:
conditions.append(DeploymentConditions.by_status_in(lifecycles))
if f.status.not_equals is not None:
lifecycles = _status_to_lifecycles(ModelDeploymentStatus(f.status.not_equals))
if lifecycles:
conditions.append(DeploymentConditions.by_status_not_in(lifecycles))
if f.status.not_in is not None:
lifecycles = _statuses_to_lifecycles([
ModelDeploymentStatus(s) for s in f.status.not_in
])
if lifecycles:
conditions.append(DeploymentConditions.by_status_not_in(lifecycles))
if f.open_to_public is not None:
conditions.append(DeploymentConditions.by_open_to_public(f.open_to_public))
if f.tags is not None:
condition = self.convert_string_filter(
f.tags,
contains_factory=DeploymentConditions.by_tag_contains,
equals_factory=DeploymentConditions.by_tag_equals,
starts_with_factory=DeploymentConditions.by_tag_starts_with,
ends_with_factory=DeploymentConditions.by_tag_ends_with,
in_factory=DeploymentConditions.by_tag_in,
)
if condition is not None:
conditions.append(condition)
if f.endpoint_url is not None:
condition = self.convert_string_filter(
f.endpoint_url,
contains_factory=DeploymentConditions.by_url_contains,
equals_factory=DeploymentConditions.by_url_equals,
starts_with_factory=DeploymentConditions.by_url_starts_with,
ends_with_factory=DeploymentConditions.by_url_ends_with,
in_factory=DeploymentConditions.by_url_in,
)
if condition is not None:
conditions.append(condition)
conditions.extend(self._convert_deployment_filter(input.filter))
orders: list[QueryOrder] = (
self._convert_deployment_orders(input.order) if input.order else []
)
Expand Down Expand Up @@ -691,63 +636,7 @@ async def project_search(
"""Search deployments within a specific project."""
conditions: list[QueryCondition] = []
if input.filter:
f = input.filter
if f.name is not None:
condition = self.convert_string_filter(
f.name,
contains_factory=DeploymentConditions.by_name_contains,
equals_factory=DeploymentConditions.by_name_equals,
starts_with_factory=DeploymentConditions.by_name_starts_with,
ends_with_factory=DeploymentConditions.by_name_ends_with,
in_factory=DeploymentConditions.by_name_in,
)
if condition is not None:
conditions.append(condition)
if f.status is not None:
if f.status.equals is not None:
lifecycles = _status_to_lifecycles(ModelDeploymentStatus(f.status.equals))
if lifecycles:
conditions.append(DeploymentConditions.by_status_in(lifecycles))
if f.status.in_ is not None:
lifecycles = _statuses_to_lifecycles([
ModelDeploymentStatus(s) for s in f.status.in_
])
if lifecycles:
conditions.append(DeploymentConditions.by_status_in(lifecycles))
if f.status.not_equals is not None:
lifecycles = _status_to_lifecycles(ModelDeploymentStatus(f.status.not_equals))
if lifecycles:
conditions.append(DeploymentConditions.by_status_not_in(lifecycles))
if f.status.not_in is not None:
lifecycles = _statuses_to_lifecycles([
ModelDeploymentStatus(s) for s in f.status.not_in
])
if lifecycles:
conditions.append(DeploymentConditions.by_status_not_in(lifecycles))
if f.open_to_public is not None:
conditions.append(DeploymentConditions.by_open_to_public(f.open_to_public))
if f.tags is not None:
condition = self.convert_string_filter(
f.tags,
contains_factory=DeploymentConditions.by_tag_contains,
equals_factory=DeploymentConditions.by_tag_equals,
starts_with_factory=DeploymentConditions.by_tag_starts_with,
ends_with_factory=DeploymentConditions.by_tag_ends_with,
in_factory=DeploymentConditions.by_tag_in,
)
if condition is not None:
conditions.append(condition)
if f.endpoint_url is not None:
condition = self.convert_string_filter(
f.endpoint_url,
contains_factory=DeploymentConditions.by_url_contains,
equals_factory=DeploymentConditions.by_url_equals,
starts_with_factory=DeploymentConditions.by_url_starts_with,
ends_with_factory=DeploymentConditions.by_url_ends_with,
in_factory=DeploymentConditions.by_url_in,
)
if condition is not None:
conditions.append(condition)
conditions.extend(self._convert_deployment_filter(input.filter))
orders: list[QueryOrder] = (
self._convert_deployment_orders(input.order) if input.order else []
)
Expand Down Expand Up @@ -1662,17 +1551,18 @@ def _convert_deployment_filter(self, f: DeploymentFilter) -> list[QueryCondition
for sub in f.AND:
conditions.extend(self._convert_deployment_filter(sub))
if f.OR:
or_conditions: list[QueryCondition] = []
or_groups: list[QueryCondition] = []
for sub in f.OR:
or_conditions.extend(self._convert_deployment_filter(sub))
if or_conditions:
conditions.append(combine_conditions_or(or_conditions))
sub_conditions = self._convert_deployment_filter(sub)
if sub_conditions:
or_groups.append(combine_conditions_and(sub_conditions))
if or_groups:
conditions.append(combine_conditions_or(or_groups))
if f.NOT:
not_conditions: list[QueryCondition] = []
for sub in f.NOT:
not_conditions.extend(self._convert_deployment_filter(sub))
if not_conditions:
conditions.append(negate_conditions(not_conditions))
sub_conditions = self._convert_deployment_filter(sub)
if sub_conditions:
conditions.append(negate_conditions(sub_conditions))
return conditions

def _build_deployment_querier(self, input: AdminSearchDeploymentsInput) -> BatchQuerier:
Expand Down Expand Up @@ -1753,17 +1643,18 @@ def _convert_revision_filter(self, f: RevisionFilter) -> list[QueryCondition]:
for sub in f.AND:
conditions.extend(self._convert_revision_filter(sub))
if f.OR:
or_conditions: list[QueryCondition] = []
or_groups: list[QueryCondition] = []
for sub in f.OR:
or_conditions.extend(self._convert_revision_filter(sub))
if or_conditions:
conditions.append(combine_conditions_or(or_conditions))
sub_conditions = self._convert_revision_filter(sub)
if sub_conditions:
or_groups.append(combine_conditions_and(sub_conditions))
if or_groups:
conditions.append(combine_conditions_or(or_groups))
if f.NOT:
not_conditions: list[QueryCondition] = []
for sub in f.NOT:
not_conditions.extend(self._convert_revision_filter(sub))
if not_conditions:
conditions.append(negate_conditions(not_conditions))
sub_conditions = self._convert_revision_filter(sub)
if sub_conditions:
conditions.append(negate_conditions(sub_conditions))
return conditions

def _build_revision_querier(
Expand Down Expand Up @@ -1814,17 +1705,18 @@ def _convert_route_filter(self, f: RouteFilter) -> list[QueryCondition]:
for sub in f.AND:
conditions.extend(self._convert_route_filter(sub))
if f.OR:
or_conditions: list[QueryCondition] = []
or_groups: list[QueryCondition] = []
for sub in f.OR:
or_conditions.extend(self._convert_route_filter(sub))
if or_conditions:
conditions.append(combine_conditions_or(or_conditions))
sub_conditions = self._convert_route_filter(sub)
if sub_conditions:
or_groups.append(combine_conditions_and(sub_conditions))
if or_groups:
conditions.append(combine_conditions_or(or_groups))
if f.NOT:
not_conditions: list[QueryCondition] = []
for sub in f.NOT:
not_conditions.extend(self._convert_route_filter(sub))
if not_conditions:
conditions.append(negate_conditions(not_conditions))
sub_conditions = self._convert_route_filter(sub)
if sub_conditions:
conditions.append(negate_conditions(sub_conditions))
return conditions

def _build_route_querier(
Expand Down Expand Up @@ -1886,17 +1778,18 @@ def _convert_access_token_filter(self, f: AccessTokenFilter) -> list[QueryCondit
for sub in f.AND:
conditions.extend(self._convert_access_token_filter(sub))
if f.OR:
or_conditions: list[QueryCondition] = []
or_groups: list[QueryCondition] = []
for sub in f.OR:
or_conditions.extend(self._convert_access_token_filter(sub))
if or_conditions:
conditions.append(combine_conditions_or(or_conditions))
sub_conditions = self._convert_access_token_filter(sub)
if sub_conditions:
or_groups.append(combine_conditions_and(sub_conditions))
if or_groups:
conditions.append(combine_conditions_or(or_groups))
if f.NOT:
not_conditions: list[QueryCondition] = []
for sub in f.NOT:
not_conditions.extend(self._convert_access_token_filter(sub))
if not_conditions:
conditions.append(negate_conditions(not_conditions))
sub_conditions = self._convert_access_token_filter(sub)
if sub_conditions:
conditions.append(negate_conditions(sub_conditions))
return conditions

def _build_access_token_querier(
Expand Down Expand Up @@ -1955,17 +1848,18 @@ def _convert_auto_scaling_rule_filter(self, f: AutoScalingRuleFilter) -> list[Qu
for sub in f.AND:
conditions.extend(self._convert_auto_scaling_rule_filter(sub))
if f.OR:
or_conditions: list[QueryCondition] = []
or_groups: list[QueryCondition] = []
for sub in f.OR:
or_conditions.extend(self._convert_auto_scaling_rule_filter(sub))
if or_conditions:
conditions.append(combine_conditions_or(or_conditions))
sub_conditions = self._convert_auto_scaling_rule_filter(sub)
if sub_conditions:
or_groups.append(combine_conditions_and(sub_conditions))
if or_groups:
conditions.append(combine_conditions_or(or_groups))
if f.NOT:
not_conditions: list[QueryCondition] = []
for sub in f.NOT:
not_conditions.extend(self._convert_auto_scaling_rule_filter(sub))
if not_conditions:
conditions.append(negate_conditions(not_conditions))
sub_conditions = self._convert_auto_scaling_rule_filter(sub)
if sub_conditions:
conditions.append(negate_conditions(sub_conditions))
return conditions

def _build_auto_scaling_rule_querier(
Expand Down Expand Up @@ -2068,17 +1962,18 @@ def _convert_replica_filter(self, f: ReplicaFilter) -> list[QueryCondition]:
for sub in f.AND:
conditions.extend(self._convert_replica_filter(sub))
if f.OR:
or_conditions: list[QueryCondition] = []
or_groups: list[QueryCondition] = []
for sub in f.OR:
or_conditions.extend(self._convert_replica_filter(sub))
if or_conditions:
conditions.append(combine_conditions_or(or_conditions))
sub_conditions = self._convert_replica_filter(sub)
if sub_conditions:
or_groups.append(combine_conditions_and(sub_conditions))
if or_groups:
conditions.append(combine_conditions_or(or_groups))
if f.NOT:
not_conditions: list[QueryCondition] = []
for sub in f.NOT:
not_conditions.extend(self._convert_replica_filter(sub))
if not_conditions:
conditions.append(negate_conditions(not_conditions))
sub_conditions = self._convert_replica_filter(sub)
if sub_conditions:
conditions.append(negate_conditions(sub_conditions))
return conditions

def _build_replica_querier(
Expand Down Expand Up @@ -2160,17 +2055,18 @@ def _convert_allocated_slot_filter(
for sub in filter_.AND:
conditions.extend(self._convert_allocated_slot_filter(sub, conditions_cls))
if filter_.OR:
or_conds: list[QueryCondition] = []
or_groups: list[QueryCondition] = []
for sub in filter_.OR:
or_conds.extend(self._convert_allocated_slot_filter(sub, conditions_cls))
if or_conds:
conditions.append(combine_conditions_or(or_conds))
sub_conditions = self._convert_allocated_slot_filter(sub, conditions_cls)
if sub_conditions:
or_groups.append(combine_conditions_and(sub_conditions))
if or_groups:
conditions.append(combine_conditions_or(or_groups))
if filter_.NOT:
not_conds: list[QueryCondition] = []
for sub in filter_.NOT:
not_conds.extend(self._convert_allocated_slot_filter(sub, conditions_cls))
if not_conds:
conditions.append(negate_conditions(not_conds))
sub_conditions = self._convert_allocated_slot_filter(sub, conditions_cls)
if sub_conditions:
conditions.append(negate_conditions(sub_conditions))
return conditions

# ------------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions src/ai/backend/manager/repositories/base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
execute_upserter,
)
from .utils import (
combine_conditions_and,
combine_conditions_or,
negate_conditions,
)
Expand Down Expand Up @@ -174,6 +175,7 @@
"BatchPurgerResult",
"execute_batch_purger",
# Utils
"combine_conditions_and",
"combine_conditions_or",
"negate_conditions",
]
17 changes: 17 additions & 0 deletions src/ai/backend/manager/repositories/base/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,23 @@ def inner() -> sa.sql.expression.ColumnElement[bool]:
return inner


def combine_conditions_and(conditions: list[QueryCondition]) -> QueryCondition:
"""Combine multiple QueryConditions with AND logic.

Args:
conditions: List of QueryCondition callables to combine

Returns:
A single QueryCondition that applies all conditions with AND logic
"""

def inner() -> sa.sql.expression.ColumnElement[bool]:
clauses = [cond() for cond in conditions]
return sa.and_(*clauses)

return inner


def negate_conditions(conditions: list[QueryCondition]) -> QueryCondition:
"""Negate multiple QueryConditions with NOT logic.

Expand Down
Loading
Loading