@@ -600,29 +600,72 @@ have kept a `null`-valued field.
600600
601601### Filtering Across Relationships
602602
603- < Callout type = " warn " >
604- ** Relation traversal inside ` where ` is not supported. ** Neither the nested form
605- ( ` where: { account: { industry: 'tech' } } ` ) nor a dotted path
606- ( ` where: { 'account.industry': 'tech' } ` ) is resolved. ` SqlDriver.applyFilters() ` only
607- recognises a nested object as an operator map when its keys start with ` $ ` ; anything
608- else is compiled as a comparison against a single column of the queried table, and a
609- dotted key is emitted verbatim, so Knex renders it as ` "account"." industry" ` against a
610- table that was never joined.
611- </ Callout >
603+ Filter on a related record's fields by nesting the condition beneath the relation
604+ field in ` where ` :
605+
606+ ``` typescript
607+ // Opportunities whose account is in the tech industry
608+ const opportunities = await engine . find ( ' opportunity ' , {
609+ where: { account: { industry: ' tech ' } },
610+ });
611+ ```
612612
613- Filter on the local foreign key, or run two queries:
613+ A condition on a related record's fields beneath a relation field (` lookup ` ,
614+ ` master_detail ` , ` user ` , ` tree ` ) is served in ` where ` : the engine reads the
615+ related object with it ** as the caller** , then matches the field against the
616+ ids it returns (` $in ` ; any member when ` multiple: true ` ).
617+
618+ Limits — one level: every key a field the related object declares, no relation
619+ or dotted key inside; forward only: never a parent by its children; ` where `
620+ only: an aggregation's ` filter ` and ` having ` refuse it, ` INVALID_FILTER ` / 400;
621+ at most 1000 related ids, refused past that, ` INVALID_FILTER ` / 400, never
622+ truncated; as the caller: the related object's row scope and field permissions
623+ apply, so a field the caller cannot read is refused, ` PERMISSION_DENIED ` / 403,
624+ never an empty result.
625+
626+ - ** Every verb that takes a ` where ` ** serves it — ` find ` , ` findOne ` , ` count ` ,
627+ ` aggregate ` , ` update ` and ` delete ` — and so do the REST ` GET /data/:object ` and
628+ ` POST /data/:object/query ` doors, which read through the same engine calls. A
629+ single-valued relation matches with ` $in ` on the related ids; a multi-valued one
630+ (` multiple: true ` ) matches a record when ** any** member qualifies, written as one
631+ ` $contains ` per id under an ` $or ` .
632+ - ** A second level is refused.** ` { account: { owner: { region: 'NA' } } } ` and
633+ ` { account: { 'owner.region': 'NA' } } ` answer ` INVALID_FILTER ` / 400.
634+ - ** The reverse direction is not served.** A parent filtered by its children is not
635+ written as ` { opportunities: { stage: 'won' } } ` : the parent has no such field, so the
636+ REST doors answer ` INVALID_FIELD ` / 400.
637+ - ** A dotted path is still refused.** ` where: { 'account.industry': 'tech' } ` answers
638+ ` INVALID_FIELD ` / 400 at the engine and at the REST doors; the message names the
639+ nested spelling to write instead.
640+
641+ Past the 1000-id cap, and for the reverse direction, run the two steps yourself —
642+ filter the related object, then ` $in ` its ids:
614643
615644``` typescript
645+ // Past the cap: read the related ids, then match the local key
616646const techAccounts = await engine .find (' account' , {
617647 where: { industry: ' tech' , annual_revenue: { $gt: 1000000 } },
618648 fields: [' id' ],
619649});
620650
621651const opportunities = await engine .find (' opportunity' , {
622- where: { account_id: { $in: techAccounts .map ((a ) => a .id ) } },
652+ where: { account: { $in: techAccounts .map ((a ) => a .id ) } },
653+ });
654+
655+ // Reverse: accounts that have a won opportunity
656+ const won = await engine .find (' opportunity' , {
657+ where: { stage: ' won' },
658+ fields: [' account' ],
659+ });
660+
661+ const accounts = await engine .find (' account' , {
662+ where: { id: { $in: won .map ((o ) => o .account ) } },
623663});
624664```
625665
666+ On a ` multiple: true ` relation, match the ids with one ` $contains ` per id under an
667+ ` $or ` instead of ` $in ` .
668+
626669### Filtering on a ` formula ` field
627670
628671<Callout type = " warn" >
0 commit comments