Skip to content

Composite-primary-key row pages return HTTP 500 (unbound bind parameter) on a malformed PK URL, should be 400 #2811

Description

@DataDawnFoundation

A GET for a row page on a table with a composite (multi-column) primary key returns HTTP 500 (sqlite3.ProgrammingError, unbound bind parameter) when the URL has the wrong number of comma-separated primary-key components — most commonly when the separating comma arrives percent-encoded as %2C (proxies and crawlers routinely re-encode reserved characters in paths).

Single-PK tables don't hit this: a non-canonical single-PK URL 404s, and handle_404's %→tilde redirect recovers it to the canonical form. The identical pk-count mismatch is already guarded with BadRequest in _fragment_request_for_row, but the row-page resolution path has no such guard — so it raises an uncaught ProgrammingError (500) before the 404 handler can run.

Reproduce (confirmed on 0.65.2 and 1.0a35)

sqlite3 demo.db "create table t (a text, b text, primary key (a, b)); insert into t values ('x', 'y');"
datasette demo.db
curl -s -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1:8001/demo/t/x,y'     # 200
curl -s -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1:8001/demo/t/x%2Cy'   # 500  <-- bug

Server log for the %2C request:

ERROR: ... sql = 'select * from t where "a"=:p0 AND "b"=:p1', params = {'p0': 'x%2Cy'}: You did not supply a value for binding parameter :p1.
sqlite3.ProgrammingError: You did not supply a value for binding parameter :p1.

Root cause

The row resolver builds one :pN placeholder per primary-key column but binds only the supplied components. When %2C collapses the comma-separated arity (one value where the table has two PK columns), the trailing :p1 is left unbound → ProgrammingError (a 500) at execute time, before any 404 — so the existing handle_404 recovery never runs.

Suggested fix

Guard the pk-value count against the pk-column count in the row resolver, raising BadRequest, mirroring the existing _fragment_request_for_row check:

if len(pk_values) != len(pks):
    raise BadRequest("Invalid primary key value count for this table")

A malformed-arity row URL is a client error, so 400 is the appropriate status — consistent with _fragment_request_for_row and with the ?_sort errors → 400 change (#1950).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions