Skip to content

Commit 91d09be

Browse files
qq9340100claude
andauthored
docs(objectql): document the FILTER-axis formula refusal and its denormalise remedy (#8372) (#8647)
`query-syntax.mdx` carried the denormalise-onto-a-stored-field remedy on the SORT axis (a Callout under "Sorting on Related Fields") and, in the search axis' own vocabulary, under "Searching by a related record's title". The FILTER axis grew the same refusal in #8296 and the page never gained it. Adds "### Filtering on a `formula` field" to §2, quoting the message the ingress door actually emits rather than paraphrasing it, and recording the two facts a caller needs beyond the remedy: the code is `INVALID_FIELD` (not `INVALID_FILTER` — the verdict is about the name's TYPE, not the value's shape), and `summary`/`autonumber` are deliberately not refused because both get real stored columns. Docs only: no code, no message text, no rule changed. Claude-Session: https://claude.ai/code/session_01Jqe56GnYFddggeAyfkZFVz Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4bfe1a5 commit 91d09be

1 file changed

Lines changed: 35 additions & 0 deletions

File tree

content/docs/protocol/objectql/query-syntax.mdx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,41 @@ const opportunities = await engine.find('opportunity', {
543543
});
544544
```
545545

546+
### Filtering on a `formula` field
547+
548+
<Callout type="warn">
549+
**Do not filter on a `formula` field.** A `formula` value is computed on read — no
550+
driver materialises a column for it — so the predicate reaches the driver, matches
551+
nothing, and the query answers an **empty list under a 200**. Both directions are
552+
wrong and `false` is the dangerous one: `where: { is_open: false }` returns no
553+
records where the same test against a stored boolean returns *every* record. The
554+
formula still reads correctly in that same response (the engine hydrates it after
555+
the driver returns), so the field is visibly populated and simultaneously
556+
unfilterable.
557+
558+
Since #8296 both doors refuse it with `400 INVALID_FIELD` — the REST/protocol
559+
ingress and `engine.find()` alike:
560+
561+
```text
562+
Query parameter 'where' filters on 'is_open', a virtual 'formula' field on object
563+
'showcase_task'. Its value is computed on read and never stored, so no driver
564+
materializes a column to filter on: the predicate reaches the driver, matches
565+
nothing, and the query answers an empty list under a 200 — in BOTH directions, so a
566+
false test returns no records where the same test against a stored boolean returns
567+
every record. Denormalise the value onto 'showcase_task' (a stored field, written
568+
when the source changes) and filter that.
569+
```
570+
571+
`INVALID_FIELD`, not `INVALID_FILTER`: the verdict is about the **name's type**, not
572+
the value's shape. The remedy is the same one the sort axis prescribes — denormalise
573+
the value onto the queried object as a **stored** field, one this object's own rows
574+
carry, written when the source changes, and filter that.
575+
576+
`summary` and `autonumber` are **not** refused: both get real, stored columns and
577+
filter correctly. A dotted filter path has no verdict on this axis — it is judged by
578+
the relation rule above.
579+
</Callout>
580+
546581
---
547582

548583
## 3. Sorting

0 commit comments

Comments
 (0)