Skip to content

Instantly share code, notes, and snippets.

@Yogu
Last active June 17, 2019 16:59
Show Gist options
  • Save Yogu/f02c9718407c7794fdd1b2c5f446affa to your computer and use it in GitHub Desktop.
Save Yogu/f02c9718407c7794fdd1b2c5f446affa to your computer and use it in GitHub Desktop.
cruddl aggregators (draft)

Collect fields

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.

Basics

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.

Flattening tree structure

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.

Collecting scalar values

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).

Aggregating values

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.

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