This commit is contained in:
Chen Yufeiyang 2026-10-13 06:42:56 +08:00 • committed by GitHub
commit 394e05ac50
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 46 additions and 3 deletions

View file

@ -2140,10 +2140,10 @@ async def table_view_data(
count_sql = f"select count(*) {from_sql}"
# Handle pagination driven by ?_next=
_next = _next or request.args.get("_next")
_next = _next if _next is not None else request.args.get("_next")
offset = ""
if _next:
if _next is not None:
sort_value = None
if is_view:
# _next is an offset
@ -2397,7 +2397,7 @@ async def table_view_data(
data = {
"ok": True,
"next": next_value and str(next_value) or None,
"next": None if next_value is None else str(next_value),
"next_url": next_url,
}
data.update(

View file

@ -366,6 +366,49 @@ async def test_page_size_zero(ds_client):
assert None is response.json()["next_url"]
def test_paginate_table_with_empty_string_primary_key():
# path_from_row_pks() returns "" for a row whose only primary key
# value is the empty string, and "" is a legitimate _next= cursor.
# The "next" key and the _next= handling both used a truthiness
# check, so the token was dropped and pagination never advanced.
with make_app_client(
memory=True,
extra_databases={
"empty_pk": (
"CREATE TABLE t (id TEXT PRIMARY KEY);"
"INSERT INTO t (id) VALUES ('a'), (''), ('c'), ('d'), ('e');"
)
},
) as client:
fetched = []
path = "/empty_pk/t.json?_size=1"
page = 0
while path:
page += 1
response = client.get(path)
assert response.status == 200
data = response.json
fetched.extend(row["id"] for row in data["rows"])
if data["next_url"]:
# next_url and next agree, per docs/json_api.rst
assert (
urllib.parse.urlencode({"_next": data["next"]}) in data["next_url"]
)
path = data["next_url"]
if path:
path = path.replace("http://localhost", "")
assert page < 30, "Possible infinite loop detected"
# Every row exactly once
assert 5 == len(fetched)
assert ["", "a", "c", "d", "e"] == sorted(fetched)
assert 5 == page
# And the final page has both keys null, as documented
last = client.get("/empty_pk/t.json?_size=100").json
assert None is last["next"]
assert None is last["next_url"]
@pytest.mark.asyncio
async def test_paginate_compound_keys(ds_client):
fetched = []