Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/17857-distinct-unresolvable-column-attribution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
"@objectstack/driver-sql": patch
---

`SqlDriver.distinct()` now answers **one unresolvable column the way the other three read doors do** — a `400` that names it — instead of a `DATABASE_ERROR` / `500` server fault.

The same condition (a column name the table does not have) asked at four doors used to get three answers and one server fault. Measured on `origin/main` at `dbea1756d9`, embedded SQLite, and identical on live PostgreSQL 16.13:

| door | before | after |
|:--|:--|:--|
| `count(t, { where: { nosuchcol: 1 } })` | `INVALID_FILTER` / 400 | unchanged |
| `find(t, { where: { nosuchcol: 1 } })` | `INVALID_FILTER` / 400 | unchanged |
| `aggregate(t, { groupBy: ['nosuchcol'] })` | `INVALID_FIELD` / 400 | unchanged |
| `distinct(t, 'title', { nosuchcol: 1 })` | **`DATABASE_ERROR` / 500** | **`INVALID_FILTER` / 400** |
| `distinct(t, 'nosuchcol')` | **`DATABASE_ERROR` / 500** | **`INVALID_FIELD` / 400** |

A caller's own mistake — a field name that does not exist — was served as a server fault naming nothing they could act on, one door away from a `400` that names the column. A picklist-populating `distinct()` sits beside the `find()` and `count()` of the same list view.

**Attribution comes from the caller's own request, never from the backend's prose.** The dialect names the column but not the clause, so the clause is read off the call this driver just compiled — the shape `aggregateBackendFault` established for `aggregate()`:

1. the name **equals the `field` argument** ⇒ `INVALID_FIELD` / 400 naming the listed column, with the `field` and `object` riders the ingress door's refusals carry;
2. it does not ⇒ the statement's only remaining column sources are the WHERE compiled from `filters` and the tenant-scope predicate, both filters, so the existing `INVALID_FILTER` refusal applies verbatim — the same sentence `find()` and `count()` give;
3. the dialect wording yields **no name** ⇒ no attribution is supportable and the terminal `DATABASE_ERROR` / 500 envelope stands unchanged.

Arm 2 is the **complement** of arm 1 rather than a search of the `filters` AST, which keeps a nested filter (`{ $or: [{ nosuchcol: 1 }] }`) on the same `400` as a flat one.

⛔ **No input that was refused before is accepted now, and no exported symbol moves.** The call fails either way; what changes is the refusal's code, status and words. No error code is minted — `INVALID_FIELD` is a standard-catalog member (ADR-0112) and already this repo's answer for a named column an object does not have. No new dialect recognizer is added: both predicates are the ones `find()`, `count()` and `aggregate()` already share.

A caller that branched on `DATABASE_ERROR` / `500` for a mistyped `distinct()` field or filter key now sees `INVALID_FIELD` / `INVALID_FILTER` `400`s instead; that is the point of the change, and it matches what the same mistake already returned from every other read door.
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,12 @@
* compiled from `filters`. A blanket arm would tell the author of
* `distinct(o, 'nosuchcol')` — who passed no filter at all — that their FILTER
* was wrong. #11541 closed that gap for `aggregate()` with a clause-attributing
* classifier; the `distinct()` half is filed as its own card. What this suite
* pins is unchanged by that: an error the classifier does NOT claim still
* leaves as this terminal envelope.
* classifier; #17857 has since closed the `distinct()` half the same way
* (`SqlDriver.distinctBackendFault`, pinned by
* `sql-driver-17857-distinct-unresolvable-column-refusal.test.ts`). What this
* suite pins was unchanged by that, exactly as this note anticipated: an error
* the classifier does NOT claim — a table that was never provisioned, a `json`
* column with no equality operator — still leaves as this terminal envelope.
*/

import { describe, it, expect, beforeAll, afterAll } from 'vitest';
Expand Down
Loading
Loading