With the @collect
directive, you can define fields that are not persisted but rather compute their value when queried, based on other fields. It allows you to follow a path of relations, child entities and other fields, collect these values and optionally apply aggregations on them.
Relations and child entity let you define a graph of objects that can be selected by regular GraphQL fields. If you're not interested in the graph structure but only in the objects, you can define a @collect
field that follows a path and collects all objects on the way:
type OrderItem @childEntity {
itemNumber: String
}
type Order @rootEntity {
items: [OrderItem]
}
type Shipment @rootEntity {
orders: [Order] @relation
allItems: [OrderItem] @collect(path: "orders.items")
}
The field allItems
will return all items in all orders of a shipment. It will not be available for filtering or sorting and you will not be able to set it directly in create and update mutations.
The path can traverse an arbitrary number of fields. Only the objects of the last field will be returned, and the type of that last field needs to match the traversal field type (OrderItem
in the example). References can not yet be followed, but you can use other traversal fields in the path.
If you have a root entity with a relation to itself, you can use a collect field to flatten the tree:
type HandlingUnit {
childHandlingUnits: [HandlingUnit] @relation
parentHandlingUnit: HandlingUnit @relation(inverseOf: "childHandlingUnits")
allInnerHandlingUnits: [HandlingUnit] @collect(path: "childHandlingUnits{1,3}")
}
The field allInnerHandlingUnits
will result in the direct children, their children, and their children (by default, in depth-first order). The first number (1
) is the minimum depth (which can also be 0
to include the originating entity), and the second number (3
) is the maximum depth. If you omit the maximum depth, the minimum depth will be used as maximum depth. It's not possible to entirely omit the maximum depth.
The minimum and maximum depth can only be specified on directly recursive relations. It is not possible to cycle through indirectly recursive relations, and child entity don't support this feature at all.
A collect path can also end in a scalar field. In the future, you can use this to e.g. get all distinct String
values of a list. Currently, you can only use it combined with an aggregation (see next section).
With the optional aggregate
argument, you can perform an aggregation on all collected items. For example, this allows you to sum up numbers:
type OrderItem @childEntity {
itemNumber: String
quantity: Int
}
type Order @rootEntity {
items: [OrderItem]
totalQuantity: Int @collect(path: "items.quantity", aggregate: SUM)
}
The path can use all the features from above and also use other @collect
fields (but not nested aggregations at the moment).
The following operators are supported:
Operator | Description | Supported Types | Null values | Result on empty list |
---|---|---|---|---|
COUNT |
Total number of items (including null ) |
all types (last segment must be a list) | included | 0 |
SOME |
true if there are any items (including null ) |
all types (last segment must be a list) | included | false |
NONE |
true if the list is empty |
all types (last segment must be a list) | included | true |
COUNT_NULL |
Number of items that are null |
all nullable types | see description | 0 |
COUNT_NOT_NULL |
Number of items that are not null |
all nullable types | see description | 0 |
SOME_NULL |
true if there are items that are null |
all nullable types | see description | false |
SOME_NOT_NULL |
true if there are items that are not null |
all nullable types | see description | false |
EVERY_NULL |
true if there are no items that are not null |
all nullable types | see description | true |
NONE_NULL |
true if there are no items that are null |
all nullable types | see description | true |
MIN |
Minimum value (ignoring null ) |
Int , Float , DateTime , LocalDate , LocalTime |
excluded | null |
MAX |
Maximum value (ignoring null ) |
Int , Float , DateTime , LocalDate , LocalTime |
excluded | null |
SUM |
Sum (ignoring null ) |
Int , Float |
excluded | 0 |
AVERAGE |
Sum / Count (ignoring null ) |
Int , Float |
excluded | null |
COUNT_TRUE |
Number of items that are true |
Boolean |
≙ false |
0 |
COUNT_NOT_TRUE |
Number of items that are not true |
Boolean |
≙ false |
0 |
SOME_TRUE |
true if there are items that are true |
Boolean |
≙ false |
false |
SOME_NOT_TRUE |
true if there are items that are not true |
Boolean |
≙ false |
false |
EVERY_TRUE |
true if there are no items that are not true |
Boolean |
≙ false |
true |
NONE_TRUE |
true if there are no items that are true |
Boolean |
≙ false |
true |
Note that if a value is collected multiple times, it will be used multiple times by the aggregator (e.g. counted twice). In the future, it will be possible to restrict the collected list to distinct values or entities.