[Fix] should_create_missing_views returns False for reltuples=0 (falsy zero bug)

`should_create_missing_views()` had `and result[0]["reltuples"]` which is
falsy when reltuples=0. On a fresh empty PostgreSQL table, CREATE INDEX sets
reltuples=0, causing the guard to return False and skip view creation entirely.
Views like MonthlyGlobalSpendPerKey are never created, and the
/global/spend/logs endpoint returns 500.

Fix: change to `and result[0]["reltuples"] is not None` so reltuples=0
(empty table) and reltuples=-1 (unanalyzed table) both correctly return True.

Also harden test_vertex_ai.py to return None instead of crashing with
JSONDecodeError when the spend-logs endpoint returns a non-JSON 500 response,
and add unit tests covering all three reltuples branches (0, -1, positive).
This commit is contained in:
Yuneng Jiang 2026-04-18 11:00:09 -07:00
parent ecf65f5d61
commit 9c0b73e5f4
No known key found for this signature in database
3 changed files with 41 additions and 1 deletions

View File

@ -251,7 +251,7 @@ async def should_create_missing_views(db: _db) -> bool:
and len(result) > 0
and isinstance(result[0], dict)
and "reltuples" in result[0]
and result[0]["reltuples"]
and result[0]["reltuples"] is not None
and (result[0]["reltuples"] == 0 or result[0]["reltuples"] == -1)
):
verbose_logger.debug("Should create views")

View File

@ -72,6 +72,10 @@ async def call_spend_logs_endpoint():
response = requests.get(url, headers=headers)
print("response from call_spend_logs_endpoint", response)
if response.status_code != 200:
print(f"spend logs endpoint returned {response.status_code}: {response.text}")
return None
json_response = response.json()
# get spend for today

View File

@ -130,6 +130,42 @@ async def test_create_views_reraises_undefined_function_error():
mock_db.execute_raw.assert_not_called()
@pytest.mark.asyncio
async def test_should_create_missing_views_reltuples_zero():
"""should return True when reltuples is 0 (fresh empty table)."""
from litellm.proxy.db.create_views import should_create_missing_views
mock_db = MagicMock()
mock_db.query_raw = AsyncMock(return_value=[{"reltuples": 0}])
result = await should_create_missing_views(mock_db)
assert result is True
@pytest.mark.asyncio
async def test_should_create_missing_views_reltuples_negative_one():
"""should return True when reltuples is -1 (table created, no ANALYZE yet)."""
from litellm.proxy.db.create_views import should_create_missing_views
mock_db = MagicMock()
mock_db.query_raw = AsyncMock(return_value=[{"reltuples": -1}])
result = await should_create_missing_views(mock_db)
assert result is True
@pytest.mark.asyncio
async def test_should_create_missing_views_reltuples_positive():
"""should return False when reltuples > 0 (table has data)."""
from litellm.proxy.db.create_views import should_create_missing_views
mock_db = MagicMock()
mock_db.query_raw = AsyncMock(return_value=[{"reltuples": 1000}])
result = await should_create_missing_views(mock_db)
assert result is False
@pytest.mark.asyncio
async def test_create_views_creates_view_on_undefined_table_error():
"""should treat 'undefined table' as a missing-view signal and attempt creation."""