Escape

Functions and reducers

Two kinds of function are callable without registering anything. A registered host function of the same name shadows either.

Aggregate reducers

You declare them under aggregates on the report or on a group.

Reducer Result Empty
sum:=expr total of expr 0
count number of rows 0
countDistinct:=expr distinct values of expr 0
avg:=expr mean of expr null
min:=expr smallest expr null
max:=expr largest expr null
first:=expr expr on the first row in scope null
last:=expr expr on the last row in scope null

count takes no expression. Every other reducer takes the form name:=expression. Give avg, min, max, first and last a visible marker where an empty set matters.

first and last are the only reducers that read the order of the rows they fold, so what a scope gives them matters. A report folds after where, its sort and its take. A group that declares a sort and no take folds its partition before it orders it. Neither skips a null.

Run reducers

You declare them under run and read each one as run.<name> on a detail row. They’re the eight above, plus one.

Reducer run.<name> on each row
sum:=expr cumulative sum up to and including this row
count 1-based running count
countDistinct:=expr distinct values seen so far
avg:=expr cumulative mean so far
min:=expr, max:=expr running extreme so far
first:=expr expr of the first row in this scope
last:=expr expr of this row — the identity, which is what @ already says
prev:=expr expr of the previous row, null on the first

Scope decides the reset. A run block on the report persists across the document. A run block on a group starts again at each instance. Run names can’t repeat across the report and nested groups, because every run value shares one run object.

Scalar built-ins

One value in, one value out. These are function calls only, never a reducer declaration, so "total": "round:[email protected]" is a definition error.

Function Result
round(x, n?) x to n decimal places, halves away from zero
floor(x, n?) x to n decimal places, towards negative infinity
ceil(x, n?) x to n decimal places, towards positive infinity
abs(x) the magnitude of x

They round the decimal you wrote, not the double it’s stored as, so round(2.675, 2) is 2.68.

n defaults to 0 and must be a number from 0 to 100. A fractional n truncates to whole places. Any other n is an error naming the cell and the call. A non-finite x passes through untouched rather than folding to zero.

min and max are reducer names and aren’t scalar two-argument functions. min(1, 2) is a render error saying so. trunc isn’t built in.

Inline reducers

A reducer also folds an array on the row itself.

an inline reducer
{ "value": "{{ sum(@.lines, l => l.total) }}" }

Registered functions

signature
type FunctionRegistry = Record<string, (...args: any[]) => any>

You pass the registry as the second argument to report or plan. Registered functions count as trusted host configuration, unlike the definition that calls them. A compiled report lists the ones it calls on report.functions, each as a { name, arity, doc? } signature in call-first-seen order.