Commit 465a0fa
fix(driver-sql): refuse scalar-comparison operators on JSON/multi-value columns instead of answering silently wrong (#7415)
* fix(driver-sql): refuse scalar-comparison operators on JSON/multi-value columns
A `multiple: true` field — and every other `JSON_COLUMN_TYPES` field — is
stored as a JSON TEXT column, and the equality family lowered straight to SQL
against that text with no column-type consultation. The result was a wrong
answer with a 200:
{members:{$in:[U1]}} -> 0 rows (fail-closed)
{members: U1} -> 0 rows (fail-closed)
{members:{$nin:[U1]}} -> the excluded row (fail-OPEN)
{members:{$lte:U1}} -> 1 row, lexicographic on the leading '['
`members not in ('U1')` is TRUE — the stored text genuinely is not equal to
that id — so "exclude these" compiled to "return everything". An exclusion
that silently stops excluding widens a result set, and a 200 with [] is
byte-identical to a query that legitimately matched nothing, so nothing
existed for a caller to key on.
Gate the three lowering entries on the column type, ahead of every rewrite and
both comparison emitters: the operator-object branch and the bare-value branch
of applyFilterCondition, and the plain-map loop of applyFilters. Placing it
before applyNormalizedComparison matters — a `multiple: true` datetime column
on an external object is served by the normalised whereRaw arms rather than
the plain whereIn arms, and showed the identical defect.
The refusal names the operator, the field, why the column cannot answer it,
states the filter was not applied, and prescribes $contains (or an $or of
$contains for any-of). ADR-0112 class 1 — INVALID_FILTER / 400, the same
envelope as the unknown-operator refusal, on every face that lowers a filter.
$contains / $notContains / $startsWith / $endsWith / $icontains and the null
predicates are untouched: the LIKE family matches the serialization as text
and is the only working membership spelling, and column presence is a
well-formed question whatever the column holds.
Fixes #7398
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011GCuQuqxKvWLYUGss7CXdc
* test(driver-sql): type the aggregate face's query bag instead of casting it
`check:query-options-erasure` counts an `as any` at the options position of
find/findOne/count/aggregate, and the new refusal sweep raised the test surface
249 -> 250. The cast was gratuitous: `aggregations` is on `DriverQuery`, so the
call types as written once the entry carries its `field`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011GCuQuqxKvWLYUGss7CXdc
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 1d0d7a2 commit 465a0fa
3 files changed
Lines changed: 697 additions & 0 deletions
File tree
- .changeset
- packages/drivers/driver-sql/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
0 commit comments