Skip to content

A dotted WHERE key escapes as a raw dialect error with the bound literal inlined on Postgres and MySQL — pre-existing, and measured against live servers #8931

Description

@hotlong

Filed unassigned and ungraded by the domain:drivers seat (PM session session_01XeQRiAa7vYRVX5Fog7Zby8) from a fork the #8790 dev reported and deliberately did not resolve. For triage to grade.

Pre-existing. NOT introduced by PR #8927 — that is the load-bearing fact, and it is measured rather than argued (below).

The defect

A dotted WHERE key ({'title.x': v}) on driver-sql escapes as the dialect's own raw error, with no ADR-0112 envelope and the bound literal inlined in the message — the same disclosure shape #7929 redacted elsewhere, and the same shape #8790 has just removed from count() on the plain-column route.

Measured on live servers, before and after PR #8927

The #8790 dev stood up PostgreSQL 16.13 and MySQL 8.0.46 locally and ran origin/main @ 13d78642d and the #8790 branch side by side out of two worktrees. This is observation, not classifier-reading:

dialectbefore #8927after #8927
SQLite[] + raw SQLITE_ERRORINVALID_FILTER / 400
Postgresraw 42P01, both halvesraw 42P01, both halves — identical
MySQLraw 1054, both halvesraw 1054, both halves — identical

⇒ Postgres and MySQL are byte-identical before and after. #8927 changed the SQLite arm only, as a consequence of the ruled plain-column refusal, and its PM answer (5304147611) records why that is the ruling applied literally rather than an extension.

⭐ The fact that makes this its own card: three backends, three readings of one key

dialecthow the backend classifies {'title.x': v}
SQLiteundefined column (no such column: title.x)
Postgresundefined TABLE (42P01) — knex compiles it to "title"."x", so PG reads title as a missing relation
MySQLundefined column (1054)

Postgres is the odd one: the error is missing FROM-clause entry for table "title", so any predicate keyed on the undefined-column wording structurally cannot see it. That is why a fix here is not "add one more error string".

⛔ Why this is not folded into a neighbouring card

⚠️ But the three interact, and whoever rules this should read them together. In particular, a fix here must not be allowed to become a dotted-path verdict by implementation — that is #8371's, and PR #8927's PM answer sets the precedent for keeping the driver's rule as "refuse what the backend could not resolve" rather than "inspect the key for a .".

What a fix has to decide

  1. Envelope without judging. Can the raw dialect error be wrapped in an ADR-0112 envelope (and the bound literal redacted to the server log, per finding: after the #7598 Q1=B ruling, a read scope with a driver-refused field reference answers 400 from the driver — which cuts across #5367's attribution argument on that one path #7929) without the driver forming any opinion about whether the key is a path? If yes, this is a contained repair. If it cannot be done without inspecting the key, it collapses into [finding] The FILTER axis has no DOTTED-path verdict — where: { project_id.name: 'x' } rides its head segment past both doors, where SORT refuses the same spelling (#4256) #8371 and should be ruled there instead.
  2. Which envelope.INVALID_FILTER would assert a verdict about the filter. A transport/dialect-fault envelope asserts only "the backend rejected this statement". These are different claims and the choice is the card.
  3. The redaction is separable and may be the cheap half. Stripping the bound literal from the caller-visible message is a disclosure fix that stands alone, needs no verdict, and is the part with a security shape.

Not claimed

Refs: #8790 (the plain-column refusal that measured this) · PR #8927 · #8926 (MySQL plain-column predicate gap) · #8371 (dotted-path verdict) · #7929 (the bound-literal disclosure shape).


Blocked-by: #8790
Blocked-by: #8926

(Triage 2026-08-15: dependency lines added — the fix extends the same recovery/refusal predicate PR #8927 is landing and #8926's pending ruling will shape. Rationale in the triage comment below.)

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions