|
| 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