The number of items in a set is known as its **cardinality**. A set with a
cardinality of zero is referred to as an **empty set**. A set with a
cardinality of one is known as a **singleton**.

The term **cardinality** is used to refer to both the *exact* number of
elements in a given set or a *range* of possible values. Internally, EdgeDB
tracks 5 different cardinality ranges: `Empty`

(zero elements), `One`

(a
singleton set), `AtMostOne`

(zero or one elements), `AtLeastOne`

(one or
more elements), and `Many`

(any number of elements).

EdgeDB uses this information to statically check queries for validity. For
instance, when assigning to a `required multi`

link, the value being
assigned in question *must* have a cardinality of `One`

or `AtLeastOne`

(as empty sets are not permitted).

It’s often useful to think of EdgeDB functions/operators as either
*element-wise* or *aggregate*. Element-wise operations are applied to *each
item* in a set. Aggregate operations operate on sets *as a whole*.

This is a simplification, but it’s a useful mental model when getting started with EdgeDB.

An example of an aggregate function is `count()`

. It returns the number
of elements in a given set. Regardless of the size of the input set, the result
is a singleton integer.

Copy

db>

`select count('hello');`

{1}

Copy

db>

`select count({'this', 'is', 'a', 'set'});`

{4}

Copy

db>

`select count(<str>{});`

{0}

Another example is `array_agg()`

, which converts a *set* of elements
into a singleton array.

Copy

db>

`select array_agg({1,2,3});`

{[1, 2, 3]}

By contrast, the `len()`

function is element-wise; it computes the
length of each string inside a set of strings; as such, it converts a set
of `str`

into an equally-sized set of `int64`

.

Copy

db>

`select len('hello');`

{5}

Copy

db>

`select len({'hello', 'world'});`

{5, 5}

In case of element-wise operations that accept multiple arguments, the operation is applied to a cartesian product of all the input sets.

Copy

db>

`select {'aaa', 'bbb'} ++ {'ccc', 'ddd'};`

{'aaaccc', 'aaaddd', 'bbbccc', 'bbbddd'}

Copy

db>

`select {true, false} or {true, false};`

{true, true, true, false}

By extension, if any of the input sets are empty, the result of applying an element-wise function is also empty. In effect, when EdgeDB detects an empty set, it “short-circuits” and returns an empty set without applying the operation.

Copy

db>

`select {} ++ {'ccc', 'ddd'};`

{}

Copy

db>

`select {} or {true, false};`

{}

Ultimately, the distinction between “aggregate vs element-wise” operations is
a false one. Consider the `IN`

operation.

Copy

db>

`select {1, 4} in {1, 2, 3};`

{true, false}

This operator takes two inputs. If it was “element-wise” we would expect the
cardinality of the above operation to the cartesian product of the input
cardinalities: `2 x 3 = 6`

. It it was aggregate, we’d expect a singleton
output.

Instead, the cardinality is `2`

. This operator is element-wise with respect
to the first input and aggregate with respect to the second. The “element-wise
vs aggregate” concept isn’t determined on a per-function/per-operator basis;
it determined on a per-input basis.

When defining functions, all inputs are element-wise by default. The
`set of`

type qualifier is used to
designate an input as *aggregate*. Currently this modifier is not supported
for user-defined functions, but it is used by certain standard library
functions.

Similarly the `optional`

qualifier marks the input as optional; an operation
will be executed is an optional input is empty, whereas passing an
empty set for a “standard” (non-optional) element-wise input will always
result in an empty set.

Similarly, the *output* of a function can be annotated with `set of`

and `optional`

qualifiers.

To compute the number of times a function/operator will be invoked, take the cardinality of each input and apply the following transformations, based on the type qualifier (or lack thereof) for each:

```
element-wise: N -> N
optional: N -> max(1, N)
aggregate: N -> 1
```

The ultimate cardinality of the result is the union of the results of each
invokation; as such, it depends on the *values returned* by each invokation.