5. Expressions
An expression computes one value from a row: a filter’s predicate, a derived field, a sort key, a group measure.
There is one evaluator for every stage, so anything that works in where works in derive, set,
select … as, sort keys, group measures and sub-pipelines, with the same results and the same errors.
5.1 Forms
| Form | Example |
|---|---|
| Literal | 42, "paid", true, null |
| Field path | age, profile.country, o.user (an alias, in a join) |
| Optional path | nick?, profile?.country |
| Parameter or variable | $min, $cities |
| Unary | not active, !active, -qty, +qty |
| Binary | age >= 18, price * qty |
| Grouping | (city == "Paris" or city == "Lyon") |
| Membership | status in ["paid", "pending"], "dev" in tags, region not in ["US"] |
| Range | age between 18 and 30, price not between 10 and 20 |
| Array | [qty, qty * 2, "x"] |
| Object | {"a": 1}, merge(meta, {"v": 2}) |
| Function call | round(price * qty, 2), coalesce(nick, name) |
Every form nests in every other: round(div(price * qty, qty - 1, 0), 2), max([a, b, c]) in allowed.
5.2 Precedence
From lowest to highest; binary operators associate to the left.
| Level | Operators |
|---|---|
| 1 | or, || |
| 2 | and, && |
| 3 (prefix) | not, ! |
| 4 | ==, != |
| 5 | <, <=, >, >=, in, not in, between … and … |
| 6 | +, - |
| 7 | *, /, % |
| 8 (prefix) | unary -, unary + |
As in SQL and Python, not binds looser than comparisons: not city == "Berlin" is not (city == "Berlin"),
and not active and age > 18 is (not active) and age > 18.
5.3 Semantics
| Construct | Rule |
|---|---|
| Comparisons | Numbers across integer and float; strings lexicographically; null with ==/!=; any other mix is an error |
and, or, not | Operands must be booleans. and and or short-circuit |
in, not in | The right side must be an array (a literal, a field, a parameter). Items match with ==; other kinds simply do not match |
between a and b | a <= x and x <= b |
| Arithmetic | See 4.5 |
| Text | No +: use concat |
| A bare boolean | Is a predicate: where active, where true |
5.4 Lazy evaluation
and, or, if, case and coalesce evaluate only what they need. This makes guards safe:
on users | where exists(nick) and nick != ""
on orders | derive unit = if(qty == 0, null, price / qty)
on rows | derive label = case(score >= 9, "A", score >= 7, "B", "C")
5.5 Constant folding
Once parameters and variables are bound, a conditional whose condition is constant is replaced by the branch it
takes: if(false, a, b) is b, true or x is true, coalesce(null, x) is x, and case drops its false
conditions. derive f = f writes nothing. So a switch driven by a parameter costs nothing at run time:
on ledger | derive entity = if($consolidate, null, entity) | group | by entity | sum amount | end
5.6 Function calls
Functions are called by name with parenthesized arguments; any expression may be an argument. Unknown names and wrong argument counts are refused when the query is parsed, before any data is read:
invalid expression "nope(age) > 1" for stage "where": unknown function `nope`; available functions: abs, sqrt, …
invalid expression "abs(age, 2) > 1" for stage "where": function `abs` expects 1 argument(s), got 2
The function reference lists all of them.
5.7 Expressions per stage
| Stage | Where an expression goes |
|---|---|
where | The predicate |
select | Each item: <expression> as <name> |
derive, set | Each assignment: <path> = <expression> |
sort | Each key |
group (compact) | Each item, with as |
group (compound) | by keys, measure values, where filters of measures, by orders of first/last |
bucket | The value bucketed |
pivot | rows, columns, values |
fill | from, to, step, and with values |
lookup, union | Every stage of the sub-pipeline, which can read the outer row through its alias |
let | Each value (no document fields: it is computed once, before the query runs) |