Skip to content

record:related_list cannot bind to a multi-value relationship field — RelatedList builds its parent filter as bare equality, which the driver refuses with 400 INVALID_FILTER #7299

Description

@os-warren

Measured against a live seeded app (objectstack-ai/duly#13), driver SqlDriver(better-sqlite3), objectui at the current checkout.

Repro

Object duly_assignment has assignees: Field.user({ multiple: true }) — one assignment names N people. On a sys_user record page, the natural way to list "the assignments this person is named on" is:

{
  type: 'record:related_list',
  properties: {
    objectName: 'duly_assignment',
    relationshipField: 'assignees',
    columns: ['subject', 'assigner', 'due_date', 'status'],
  },
}

RecordRelatedListBody passes referenceField + parentId to RelatedList, which composes the parent filter as bare equality — {[relationshipField]: parentId}. The request and the answer:

GET /api/v1/data/duly_assignment?top=20&skip=0&sort=due_date
    &filter=["assignees","=","ql8NO1TcKsuFrrH-"]

400 INVALID_FILTER
The bare equality spelling { "assignees": value } WAS NOT APPLIED: "assignees"
is a multi-value (or otherwise JSON-valued) field, stored by this driver as a
JSON TEXT column (e.g. ["a","b"]), and "=" compares that whole serialized text
against a single value — it can never equal one member. Use "$contains" for
membership ({ "assignees": { "$contains": "a" } }), or an $or of "$contains"
for any-of …

Why this is objectui's

Credit where due — the driver behaves exactly right. It refuses loudly, names the field, explains the storage, and prescribes the working spelling. This is not a silent-zero bug; the section renders an error and the console logs it.

The problem is that $contains is not reachable from the authoring surface. RecordRelatedListProps lets an author supply an additional filter, but the parent filter — the one that makes it a related list at all — is composed by the component from relationshipField alone. There is no operator, no relationshipOperator, nothing. So the list cannot be written correctly; it can only be not written.

Nor is there another route to the same data: RelatedList has no dotted-path column support, so the value cannot be reached from the child side either (duly_task.assignment → duly_assignment.assigner is one hop too far). On the page in question that meant one line of the requirement — "showing who assigned each" — could not be delivered at all, and the list was deleted rather than left showing a 400.

Suggested shape

Have the renderer pick the membership spelling when the parent field is multi-valued — the child object's field metadata is already in hand (useFieldPermissions(objectName) resolves the object), so multiple: true is knowable at compose time and {[relationshipField]: {$contains: parentId}} is what the driver is asking for. An explicit author-side escape (relationshipOperator: 'contains') would work too but puts the burden in the wrong place — the component knows the field's arity, the author is just naming the relationship.

Whichever way: the m2m-by-multi-value-field shape is common enough (it is the platform's own Field.user({ multiple: true })), and today it is the one relationship a related list cannot express.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions