Skip to content

Commit 6a1b45e

Browse files
os-zhuangclaude
andauthored
fix(objectql): measure the value-bearing diagnostic families instead of reading them off a manual (#9160) (#9274)
#8823 left a one-entry list of value-bearing dialect templates and no way to notice a second was missing. This adds the instrument: a live probe that plants a canary, raises each candidate family through the driver's own bind path against the MySQL 8.0 / PostgreSQL 16 services the Temporal Conformance job already stands up, and asserts where the canary lands — error.message (which ObjectLogger.write serializes) or error.detail (which it does not). Positive control first: ER_DUP_ENTRY (1062) reproduced verbatim, so a zero elsewhere is interpretable. Four more families measured value-bearing on error.message and encoded: mysql 1366/1292 (Incorrect <type> value: '<v>' for column '<c>' at row N), mysql 1292 (Truncated incorrect <TYPE> value: '<v>'), pg 22P02/22007 (invalid input syntax for type <t>: "<v>"), pg 22003 (value "<v>" is out of range for type <t>). The Postgres result is the sharp one: #8823 recorded that pg escapes the unique-violation leak only because its value sits on error.detail, which the logger never serializes -- "coincidence, not a defence". These three put the caller's value on error.message, so the coincidence does not cover them. Six identifier-only families raised by the same probe are pinned untouched, so over-matching (which deletes the diagnostic an operator came for) is a red too. Claude-Session: https://claude.ai/code/session_01NTKPDRoynY8i3HmdSFUxFj Co-authored-by: Claude <noreply@anthropic.com>
1 parent 77da09b commit 6a1b45e

4 files changed

Lines changed: 790 additions & 22 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
---
2+
"@objectstack/objectql": patch
3+
"@objectstack/driver-sql": patch
4+
---
5+
6+
fix(objectql): stop logging the caller's value for four MORE diagnostic families — measured off live MySQL 8.0 / PostgreSQL 16, not read off a manual (#9160)
7+
8+
#8823 established that a database's diagnostic does not always name only
9+
IDENTIFIERS: MySQL's `ER_DUP_ENTRY` inlines the conflicting VALUE, and
10+
`redactStatementFromMessage` redacts that one slot while keeping the index name
11+
an operator needs.
12+
13+
The list it introduced had **exactly one entry and no way to notice a second was
14+
missing**. Nothing measured whether a diagnostic a driver produced carried a
15+
value; the single entry got there because a human read one template closely, and
16+
the standing rule (`packages/types/src/unique-violation.ts`) — a dialect's
17+
spelling goes in once measured off a thrown error, never from a reading of the
18+
manual — correctly prevented the list from growing on a guess.
19+
20+
**The instrument now exists.** `sql-driver-diagnostic-value-probe.test.ts` plants
21+
a canary, raises each candidate family through the driver's own bind path against
22+
the live MySQL 8.0 / PostgreSQL 16 services the `Temporal Conformance (live PG +
23+
MySQL)` job already stands up, and asserts of every family — value-bearing or not
24+
**where the canary lands**: `error.message` (which `ObjectLogger.write`
25+
serializes, so an exposure) or `error.detail` (which it does not). A family that
26+
starts inlining a value it did not inline before is now a named red naming the
27+
file to edit, instead of a silent leak.
28+
29+
Measured with a positive control first (`ER_DUP_ENTRY`, the known-value-bearing
30+
neighbour, reproduced verbatim — without it a zero elsewhere would be
31+
uninterpretable):
32+
33+
| dialect | family | diagnostic, verbatim | verdict |
34+
|:--|:--|:--|:--|
35+
| mysql | 1062 | `Duplicate entry 'CANARY' for key 'probe.uq'` | value on `message` (already encoded) |
36+
| mysql | 1366 | `Incorrect integer value: 'CANARY' for column 'age' at row 1` | **value on `message`** |
37+
| mysql | 1292 | `Incorrect datetime value: 'CANARY' for column 'when_at' at row 1` | **value on `message`** |
38+
| mysql | 1264 | `Out of range value for column 'age' at row 1` | identifier only |
39+
| mysql | 1406 | `Data too long for column 'label' at row 1` | identifier only |
40+
| mysql | 1054 | `Unknown column 'zzz…' in 'field list'` | identifier only |
41+
| pg | 22P02 | `invalid input syntax for type integer: "CANARY"` | **value on `message`** |
42+
| pg | 22007 | `invalid input syntax for type timestamp with time zone: "CANARY"` | **value on `message`** |
43+
| pg | 22003 | `value "99999999999" is out of range for type integer` | **value on `message`** |
44+
| pg | 23505 | `duplicate key value violates unique constraint "…"` | value on `detail` only |
45+
| pg | 23502 | `null value in column "id" … violates not-null constraint` | value on `detail` only |
46+
| pg | 22001 | `value too long for type character varying(20)` | identifier only |
47+
48+
Both families the card named as candidates **are** value-bearing, and the
49+
Postgres one is the sharper result: #8823 recorded that Postgres escapes the
50+
unique-violation leak only because its value sits on `error.detail`, a field the
51+
logger never serializes — *"coincidence, not a defence"*. `22P02` / `22007` /
52+
`22003` put the caller's value on **`error.message`**, the field that IS
53+
serialized, so the coincidence does not cover them.
54+
55+
The one-off regex pair is now an enumerable `VALUE_BEARING_TEMPLATES` table, one
56+
row per measured family, each citing the live server that produced it. Every
57+
identifier-bearing tail is still kept whole — over-matching deletes the
58+
diagnostic an operator came for, which is the expensive direction #8682 paid to
59+
avoid, and the six identifier-only families above are pinned against exactly that
60+
regression.
61+
62+
**Known residue, measured and deliberately not closed here:** when the caller's
63+
value itself contains ` - `, the statement cut lands inside it and eats the
64+
template head. Families with a right anchor (`for key …`, `for column … at row
65+
N`) recover; the two whose value runs to end of message (pg 22P02/22007, mysql
66+
1292's `Truncated incorrect …` spelling) have no anchor and leave a suffix
67+
standing. Closing that requires the cut itself to become template-aware — a
68+
change to #8682's contract, filed rather than decided.

0 commit comments

Comments
 (0)