mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
fix(proxy): return no rows when the aggregated activity entity filter is empty (#37414)
The aggregated daily-activity queries build their WHERE clause as raw SQL, and an empty entity list rendered as `"team_id" IN ()`, which Postgres rejects with `syntax error at or near ")"`. Callers reach that state normally: a caller without admin view and no explicit team_ids has its scope resolved to the teams it belongs to, so anyone belonging to no teams, an org admin for instance, sent an empty list and got a 500 back from /team/daily/activity/aggregated. The paginated endpoint hands the same empty list to Prisma, which renders `in: []` and matches nothing, so it kept returning 200 with an empty result set. Emit FALSE for the empty case so the raw-SQL path lands on the same answer, mirroring what the api_key filter a few lines below already does. The fix covers both aggregated queries at once because they share one WHERE clause builder.
This commit is contained in:
parent
5f6a14dbb7
commit
7ac764970b
2 changed files with 61 additions and 5 deletions
|
|
@ -594,13 +594,16 @@ def _build_aggregated_where_clause(
|
|||
sql_params.append(adjusted_end)
|
||||
p += 1
|
||||
|
||||
# Optional entity filter
|
||||
# Optional entity filter; an empty list must match nothing, not everything
|
||||
if entity_id is not None:
|
||||
if isinstance(entity_id, list):
|
||||
placeholders = ", ".join(f"${p + i}" for i in range(len(entity_id)))
|
||||
sql_conditions.append(f'"{entity_id_field}" IN ({placeholders})')
|
||||
sql_params.extend(entity_id)
|
||||
p += len(entity_id)
|
||||
if entity_id:
|
||||
placeholders = ", ".join(f"${p + i}" for i in range(len(entity_id)))
|
||||
sql_conditions.append(f'"{entity_id_field}" IN ({placeholders})')
|
||||
sql_params.extend(entity_id)
|
||||
p += len(entity_id)
|
||||
else:
|
||||
sql_conditions.append("FALSE")
|
||||
else:
|
||||
sql_conditions.append(f'"{entity_id_field}" = ${p}')
|
||||
sql_params.append(entity_id)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ sys.path.insert(0, os.path.abspath("../../../..")) # Adds the parent directory
|
|||
from litellm.proxy.management_endpoints.common_daily_activity import (
|
||||
_adjust_dates_for_timezone,
|
||||
_build_aggregated_sql_query,
|
||||
_build_entity_rollup_sql_query,
|
||||
_is_user_agent_tag,
|
||||
_record_to_spend_metrics,
|
||||
get_api_key_metadata,
|
||||
|
|
@ -982,6 +983,58 @@ class TestBuildAggregatedSqlQuery:
|
|||
assert "COALESCE(model_group, model)" not in normalized
|
||||
|
||||
|
||||
class TestAggregatedEmptyEntityFilter:
|
||||
_BUILDERS: Final = (_build_aggregated_sql_query, _build_entity_rollup_sql_query)
|
||||
|
||||
@pytest.mark.parametrize("build", _BUILDERS)
|
||||
def test_empty_entity_list_emits_no_degenerate_in_clause(self, build):
|
||||
sql, params = build(
|
||||
table_name="litellm_dailyteamspend",
|
||||
entity_id_field="team_id",
|
||||
entity_id=[],
|
||||
start_date="2026-08-01",
|
||||
end_date="2026-08-19",
|
||||
model=None,
|
||||
api_key=None,
|
||||
)
|
||||
|
||||
normalized = " ".join(sql.split())
|
||||
assert "IN ()" not in normalized
|
||||
assert '"team_id" IN' not in normalized
|
||||
assert params == ["2026-08-01", "2026-08-19"]
|
||||
|
||||
@pytest.mark.parametrize("build", _BUILDERS)
|
||||
def test_empty_entity_list_matches_nothing_rather_than_everything(self, build):
|
||||
sql, _ = build(
|
||||
table_name="litellm_dailyteamspend",
|
||||
entity_id_field="team_id",
|
||||
entity_id=[],
|
||||
start_date="2026-08-01",
|
||||
end_date="2026-08-19",
|
||||
model=None,
|
||||
api_key=None,
|
||||
)
|
||||
|
||||
assert "FALSE" in " ".join(sql.split())
|
||||
|
||||
@pytest.mark.parametrize("build", _BUILDERS)
|
||||
def test_populated_entity_list_still_filters_on_its_ids(self, build):
|
||||
sql, params = build(
|
||||
table_name="litellm_dailyteamspend",
|
||||
entity_id_field="team_id",
|
||||
entity_id=["team-alpha", "team-beta"],
|
||||
start_date="2026-08-01",
|
||||
end_date="2026-08-19",
|
||||
model=None,
|
||||
api_key=None,
|
||||
)
|
||||
|
||||
normalized = " ".join(sql.split())
|
||||
assert '"team_id" IN ($3, $4)' in normalized
|
||||
assert "FALSE" not in normalized
|
||||
assert params == ["2026-08-01", "2026-08-19", "team-alpha", "team-beta"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_daily_activity_aggregated_empty_result_set():
|
||||
"""Regression test for the empty-range 500.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue