CodamAIDocs
Topicdone

Expanding references and lists

How a reference is fully loaded with { field, response }, how lists get their own filter, sorting and page size, and how deep this may go.

Variants
Single referencesingle reference not set or invisible → nullListList with parameterseveral levelsback reference is added automaticallyno match count for nested listsreference without read role → 403

What this is about

With * you get a reference only as { "id": … }. If you want to see more of the referenced object, you expand the reference: instead of a field name, you put an object into the response.

{ "field": "company", "response": ["companyname", "city"] }

field names the reference, response says which fields of the referenced object you want. This works for single references and for lists.

The example model

ModelFieldsReferences
companycompanyname, cityemployees (list of employee)
employeefirstname, lastnamecompany (single), department (single)
departmentname–

employee.company and company.employees are the two sides of the same relation. company is the back reference of employees.

From request to response

flowchart LR
    subgraph R["response"]
        direction TB
        r1["companyname"]
        r2["{ field: employees }"]
        r3["firstname"]
        r4["{ field: department }"]
        r5["name"]
        r2 --> r3
        r2 --> r4
        r4 --> r5
    end
    subgraph A["Response"]
        direction TB
        a1["company<br/>companyname: Codamic AG"]
        a2["employees[0]<br/>firstname: Daniel"]
        a3["department<br/>name: Development"]
        a4["employees[1]<br/>firstname: Anna"]
        a5["department<br/>name: Sales"]
        a1 --> a2
        a1 --> a4
        a2 --> a3
        a4 --> a5
    end
    R ==> A
Two levels in one request
Request
POST /api/rest/hr/company/read/a1…
{
  "response": [
    "companyname",
    {
      "field": "employees",
      "response": [
        "firstname",
        { "field": "department", "response": ["name"] }
      ]
    }
  ]
}
Response
{
  "data": {
    "id": "a1…",
    "companyname": "Codamic AG",
    "employees": [
      { "id": "7f3…", "firstname": "Daniel",
        "department": { "id": "d4…", "name": "Development" } },
      { "id": "8b1…", "firstname": "Anna",
        "department": { "id": "d7…", "name": "Sales" } }
    ]
  },
  "meta": { "error": false }
}

The response is shortened: fields you did not request are actually in it as null, and every object has its system fields _createdOn and _updatedOn.

Single reference and list work differently

How CDMS expands a reference

When: The reference points to one object, e.g. employee.company.

  1. 1
    CDMS→Database
    reads the employee and attaches company with a LEFT JOIN, taking only the id and the type
  2. 2
    CDMS
    checks the read role of company
  3. 3
    CDMS→Database
    reads the company object with this id like a read of its own: with its row filters and only with the fields from the inner response
  4. 4
    CDMS
    puts the result into the field company

Result: company is an object with the requested fields.

When: The employee has no company, or the user may not see this company (own data, attribute filters).

In the first case the LEFT JOIN returns no id, in the second the read of the company finds nothing because of the row filters. In both cases the field stays empty. The employee itself still comes back.

Result: 200 with "company": null.

When: The reference is a list, e.g. company.employees.

  1. 1
    CDMS→Database
    reads the company
  2. 2
    CDMS
    checks the read role of employee
  3. 3
    CDMS
    builds a search of its own on employee with the filter company.id = <id of the company>
    CDMS adds this filter over the back reference itself. You do not write it.
  4. 4
    CDMS→Database
    searches the matching employee objects, with their row filters and only with the fields from the inner response
  5. 5
    CDMS
    puts the matches as a list into employees

Result: employees is a list. Without matches it is empty.

When: You want only part of the list, sorted.

The object may contain a parameter, just like a search: query, order, limit, page. CDMS combines your filter with the filter on the back reference using AND. Without limit you get all entries.

Result: The list contains only the entries that match your filter, in your sorting and at most limit of them.

When: The inner response contains an object again.

Every level works exactly like the first one. There is no fixed depth limit, the limit is your response: CDMS never goes deeper than you write it.

Result: A tree that has exactly the shape of your response.

When: The response reaches into employees, the user lacks employee-read.

  1. 1
    CDMS
    checks the read role of employee
  2. 2
    CDMS→Client
    403 missing-permission|employee-read

Result: The whole request fails. The same request without the employees object works with the same role.

A list with its own filter

The five most recently created employees whose last name starts with “M”
Request
POST /api/rest/hr/company/read/a1…
{
  "response": [
    "companyname",
    {
      "field": "employees",
      "response": ["firstname", "lastname"],
      "parameter": {
        "limit": 5,
        "order": [{ "field": "_createdOn", "order": "DESC" }],
        "query": {
          "type": "AND",
          "filter": [{ "key": "lastname", "value": "M%", "param": "LIKE" }]
        }
      }
    }
  ]
}
Response
{
  "data": {
    "id": "a1…",
    "companyname": "Codamic AG",
    "employees": [
      { "id": "7f3…", "firstname": "Daniel", "lastname": "Mertins" },
      { "id": "2c9…", "firstname": "Eva", "lastname": "Meier" }
    ]
  },
  "meta": { "error": false }
}

What CDMS turns this into as a search on employee:

flowchart TB
    subgraph W["WHERE for employee"]
        direction TB
        w1["company.id = 'a1…'<br/>(added by CDMS)"]
        w2["AND lastname LIKE 'M%'<br/>(your filter)"]
        w3["AND row filters of employee<br/>(own data, attribute filters)"]
    end
    W --> O["ORDER BY _createdOn DESC"]
    O --> L["at most 5"]

How filtering, sorting and paging work in detail is described in the search chapter, among others under Filters in nested lists and Paging and match count.

In a search: one sub-query per match

If you expand a list in a search, CDMS runs the sub-query for each match separately:

sequenceDiagram
    participant C as Client
    participant D as CDMS
    participant DB as Database
    C->>D: POST /hr/company/query (limit 25) with { field: employees }
    D->>DB: search on company
    DB-->>D: 25 companies
    loop for each of the 25 companies
        D->>DB: search on employee with company.id = …
        DB-->>D: employees of this company
    end
    D-->>C: 25 companies, each with its list employees

With 25 companies these are 25 additional searches. If every company has 500 employees and you set no limit, 12,500 employees come back. More on this under What happens in the database when reading.

What a nested list does not have

Search on top and list inside
POST /query
the search itself
  • data plus meta
  • meta.totalCount tells you how many matches there are in total
  • paging with page and limit
{ field, parameter }
list inside an object
  • only the entries
  • no match count, the list is a plain JSON array
  • limit and page still work, for each list separately

If you need the total number of entries of a list, ask the child model directly: POST /hr/employee/query with the filter company.id = … returns meta.totalCount.

Decision table

What is in the field of an expanded reference?
Kindread role of the target modeltarget present and visibleContent of the field
singleno–403 for the whole request
singleyesnonull
singleyesyesobject with the requested fields
listno–403 for the whole request
listyesnoneempty list []
listyessomeonly the visible entries

Instead of the read role of the target model, a role on the relation itself can be enough. See Permissions on relations. The table describes the default, strict mode.

Pitfalls

Where to go next

Sources in the code and the knowledge base
  • CDMS/cdms-rest-api – Expander.expandModelResponse, ResponseDeserializer
  • CDMS/cdms-system-layer – AbstractLayer.recursiveRead, recursiveQuery, fetchAndSetModel, fetchAndSetList, enterField
  • CDMS/cdms-integrationtest – AbstractRecursiveRead, AbstractRoleDenialTest.readingARelationNeedsTheRelationRole, AbstractManyToManyTest
  • documentation/05-api-guide/04-lesen.md
Search