Changelog - 2026-09-26
JSON-path where and order keys are qualified
Bug FixIn one line. A where or order key on a JSON/JSONB column now renders qualified with the table - or the query alias, on an included relation - exactly like a plain column already did, in both Postgres and SQLite.
What changed
- Postgres. A JSON-path
where(#>>) andorder(#>) extraction now qualifies the column:"orders"."metadata" #>> '{tier}'instead of"metadata" #>> '{tier}'. The numeric-cast branch (::numeric) and a JSON path nested undernotqualify the same way. - SQLite.
json_extractnow takes the qualified column:json_extract("orders"."metadata", '$."tier"')instead ofjson_extract("metadata", '$."tier"'). A numeric path component that doubles the candidate reads still works - both extractions inside thecoalesce(...)qualify. - The path segment is unchanged. Only the column part renders differently; the validated path literal (
'{tier}','$."tier"') is exactly as before, and no new injection surface opens.
Who is affected
- A query that joins a second table with a same-named JSON column. Before, Postgres rejected it with
column reference "metadata" is ambiguousand SQLite withSQLITE_ERROR: ambiguous column name: metadata; it now runs on both. This is the defect this change fixes. - A test or a log line that asserts the exact rendered SQL string for a JSON-path filter or order key. It now sees the table (or alias) prefix on that fragment. Update the expected string - the filter shape and the returned rows are unchanged.
- A
FilterBuildersubclass author. The protectedbuildJsonOperatorConditionsnow receivesjsonPath/safeNumericCasttypedstring | SQL, notstring. An existing override still compiles either way (parameters are bivariant) and one that only forwards the value (tosuper, or straight into an operator function) keeps working, still qualified. But one still typedstringthat treats the value as text (sql.raw(opts.jsonPath), or interpolating it into a template string) now receives anSQLobject and emits[object Object]- the query then fails. Retype it tostring | SQLand pass the value through instead of stringifying it. - Everyone else. No action needed. The filter API, the rows returned, and their order are all unchanged.
Migration
None. This is a defect fix with no public API change - a filter such as { 'metadata.tier': 'gold' } is written exactly as before.