Fix KeyError when sorting with _labels=on and excluded sort column

Handle both IndexError and KeyError when accessing sort column in
_next_value_and_url. CustomRow objects (used with _labels=on) raise
KeyError for missing columns, while sqlite3.Row raises IndexError.

Adds tests for sorting with _labels=on when the sort column is excluded
from SELECT via _col or _nocol parameters.
This commit is contained in:
Pyry Takala 2025-11-26 23:11:00 +00:00
commit 074ac06dac
2 changed files with 66 additions and 1 deletions

View file

@ -1784,7 +1784,7 @@ async def _next_value_and_url(
if (sort or sort_desc) and not is_view:
try:
prefix = rows[-2][sort or sort_desc]
except IndexError:
except (IndexError, KeyError):
# sort/sort_desc column missing from SELECT - look up value by PK instead
prefix_where_clause = " and ".join(
"[{}] = :pk{}".format(pk, i) for i, pk in enumerate(pks)

View file

@ -339,6 +339,71 @@ async def test_sortable(ds_client, query_string, sort_key, human_description_en)
assert [r["content"] for r in expected] == [r["content"] for r in fetched]
async def _assert_pagination_works(ds_client, response):
"""Helper to verify pagination works correctly."""
assert response.status_code == 200
data = response.json()
assert "rows" in data
assert len(data["rows"]) > 0
if "next_url" in data and data["next_url"]:
next_path = data["next_url"].replace("http://localhost", "")
next_response = await ds_client.get(next_path)
assert next_response.status_code == 200
next_data = next_response.json()
assert "rows" in next_data
@pytest.mark.asyncio
@pytest.mark.parametrize(
"sort_param,exclude_param",
[
("_sort=sortable", "_col=content"),
("_sort=sortable", "_nocol=text&_nocol=sortable_with_nulls"),
("_sort_desc=sortable", "_col=content"),
],
)
async def test_labels_sort_excluded_column(ds_client, sort_param, exclude_param):
"""Test that sorting works when _labels=on and sort column is excluded from SELECT."""
path = (
f"/fixtures/sortable.json"
f"?_labels=on"
f"&{sort_param}"
f"&{exclude_param}"
f"&_shape=objects"
f"&_extra=next_url"
)
response = await ds_client.get(path)
await _assert_pagination_works(ds_client, response)
@pytest.mark.asyncio
async def test_labels_foreign_key_sort_excluded_column(ds_client):
"""
Test sorting with _labels=on when sort column is excluded from SELECT.
Uses facetable which has foreign keys and a primary key.
"""
path = (
"/fixtures/facetable.json"
"?_labels=on"
"&_sort=state"
"&_col=pk"
"&_col=_city_id"
"&_size=2"
)
response = await ds_client.get(path)
assert response.status_code == 200
data = response.json()
assert "rows" in data
assert len(data["rows"]) > 0
if "next_url" in data and data["next_url"]:
next_path = data["next_url"].replace("http://localhost", "")
next_response = await ds_client.get(next_path)
assert next_response.status_code == 200
next_data = next_response.json()
assert "rows" in next_data
@pytest.mark.asyncio
async def test_sortable_and_filtered(ds_client):
path = (