Skip to main content

Actyx Query Language (AQL)

The Actyx Query Language (AQL) allows you to select precisely which information you want to extract from the query or subscribe endpoints of the Events API. In its most basic form it selects events based on their tags. This section gives you all the gory details of how that works and what else you can express.

This chapter is normative

If you find Actyx behavior differing from the description below, then you have found a bug. Please tell us about it!

If you discover some behavior that is not documented below, then you have wandered into unspecified territory — the observed behavior may change in future releases. In these cases we very much welcome your questions, comments, and suggestions in the forum!

General structure

AQL is not whitespace sensitive, you can add any amount of whitespace in almost all places (the exceptions are documented below). Comments begin with -- and extend to the end of the line. The overall structure of a query is the following:

FEATURES(some features)  -- this is optional
FROM 'mytag1' & 'mytag2' -- the only mandatory part
... -- optional list of transformations
END -- optional

Language features

The language is growing and will continue to do so. Our intention is to keep it backward compatible for as long as we can so that you can keep running your apps also on future Actyx versions of the 2.x series. Since we need to try out extensions and get feedback on them before committing to them, new features start out in alpha or beta status and may then graduate to fully released at a later time.

Alpha features are not documented, are expected to change, and are only revealed to ask for specific feedback on the functionality. If you have questions or ideas, you are always welcome to let us know, perhaps it is easy to add or already on its way.

Beta features are documented and are expected to be more stable, although we reserve the right to change them at any time even during the Actyx 2.x series. You are very welcome to try them out and discuss about them, for example in the forum.

If your query uses a feature that is not yet released, you’ll have to enable this feature using the FEATURES() syntax. Each feature’s name is a single word and you can put any number of words between the parentheses, separated by whitespace. Leaving out the FEATURES() syntax is the same as enabling no alpha or beta features, meaning that your query can use only released features.

Each feature is described below with its required feature names, if any.

Event queries

The FROM <tag_expr> part of an AQL query selects the events from which the results shall be computed. Hereby, <tag_expr> is a boolean expression composed from the following basic atoms:

  • 'mytag' or "mytag" matches all events that carry this tag. Tags are arbitrary non-empty Unicode strings. Quoting is only needed for the used delimiter: if your tag is enclosed in single quotes, single quotes within the tag need to be repeated, e.g. 'o''clock'. The analogue goes for double quotes. Tags containing IDs that where created using an Actyx SDK can be queried as mytag:myid.

  • isLocal matches all events that were emitted by the local Actyx node.

  • allEvents matches all events.

  • FEATURES(timeRange): from(<time>) matches all events whose timestamp is greater or equal to the provided one. Timestamps are given in UTC and always end with Z — this will allow future extension towards supporting timezones. Valid formats are 2021-07-20T09:53:07.462Z, or without milliseconds, seconds, or just the date. Omitted components are treated as zero, so 2021-07-20Z marks the beginning of that day in UTC.

  • FEATURES(timeRange): to(<time>) matches all events whose timestamp is less than the provided one, i.e. to(<x>) and from(<x>) seamlessly cover the timeline around time x without overlap. The time formats are exactly the same as for from(<time>).

  • FEATURES(eventKeyRange): from(<event ID>) matches all events whose event ID is greater or equal to the provided one. An event ID consists of <lamport timestamp>/<node ID>-<stream nr> and has the same sort order as the corresponding event. You may specify only the lamport timestamp, in which case node ID and stream number are treated as zero.

  • FEATURES(eventKeyRange): to(<event ID>) matches all events whose event ID is less than the provided one. The syntax rules are the same as for from(<event ID>).

  • appId(<app ID>) matches all events from the given app ID. No whitespace is allowed between the parentheses. App IDs are valid DNS names, i.e. name components consist of lowercase letters, digits, or dashes and are separated by a single dot. A valid example is appId(

Larger expressions are constructed using the and and or combinators:

  • <tag_expr> & <tag_expr> matches all events that match both the left and the right condition
  • <tag_expr> | <tag_expr> matches all events that match at least one of the given conditions

As usual, & takes precedence over |. You can use parentheses to override this: 'a' & ('b' | 'c') is the same as 'a' & 'b' | 'a' & 'c'.

Data transformations

Following the FROM <tag_expr> clause you may optionally specify a sequence of transformation steps. The first step will receive the events selected by the event query as input and compute outputs from them. Each of the outputs is fed into the following step, where the same principle applies, etc. This allows you to write down the transformation from events into query results in an incremental fashion, doing one step at a time.

Discarding inputs with FILTER

Whenever an event query is not specific enough, e.g. because not all relevant properties are available as tags, you can filter out undesirable results within Actyx. This makes processing more efficient since it avoids serialization and deserialization plus the filtering in your application code. The syntax for a filter step is

FILTER <simple_expr>

If the provided expression (see below) evaluates to the boolean value TRUE, then the input is passed along as output. Otherwise the input is discarded.

Transforming a single input with SELECT

The events originating in your FROM <tag_expr> clause may contain more information than you need or they may not have the format you desire, e.g. different property names. In these cases, you can use a transformation step that takes a single input and computes at most one output from it using the simple expression language shown further below. The syntax for a transformation step is

SELECT <simple_expr>

Whatever the given expression (see below) evaluates to will be passed on as an output. However, it is possible that no value is computed, for example when accessing non-existent properties in the input. In such cases, no output is generated.

The AQL data model

Before discussing the expression language we need to lay the groundwork: this section describes the data types AQL works with. AQL is dynamically typed, meaning that each computed value does have exactly one type. This type is not considered when reading the query, it is only checked during the evaluation of expressions. One noteworthy difference to Javascript is that AQL does not know subtyping, and it also doesn’t coerce values from one type to the other implicitly.

  • NULL is the single value of the unit type
  • TRUE and FALSE are both values of the boolean type
  • 42 and -12.34 are examples of the number type, which currently contains either a 64bit unsigned integer or a double-precision finite floating point number
  • 'hello' or "world" are examples of the string type (with quoting rules like for tags)
  • [1, 2, 3] is an example of the array type
  • { one:1 two:2 } is an example of the object type

Simple expressions

Expressions are built up from literal values using a notation similar to the C family of languages. Whenever the description says that an error is generated, the evaluation of the whole expression stops without a result and you’ll get an error message in your query response.

  • ! <expr> or ¬ <expr> negates a boolean value.

  • <expr> & <expr>, <expr> | <expr>, and <expr> ~ <expr> compute the logical and, or, and xor of boolean values, respectively (you can also use , , and ).

  • Comparison operators >, >=, <, <=, =, != (or , , ) work between operands of the same type, i.e. comparing a number to a string yields an error.

  • Arithmetic operators +, -, *, / (with alternatives ×, ÷), % (mod), ^ (exponentiation) work between numbers, otherwise yield an error. Natural numbers (64bit integers) are converted to floating point when combined with floating point numbers. All operations yield an error upon overflow or underflow.

  • Arrays are constructed with [<expr>, ...].

  • Objects are constructed with {<key>: <expr>, ...}, where the comma separators are optional; each <key> can be either a bare word (in which case it must start with a lowercase letter, followed by lowercase letters, numbers, or underscores) or a pair of brackets containing either a natural number, a string, or an expression.

    Valid examples are {asdf: 42}, {[12]: "hello }, {['PascalCase']: TRUE}, {[1 + 1]: 2}.

  • Values are suffixed by an index to dig into arrays or objects, yielding an error if the value is of the wrong type or lacks the desired property; indexes follow the same rules as object keys, with the addition that a bare word is preceded by a dot, like in many object-oriented languages.

    Valid examples are x[0], y.my_property_42, z[2].prop['isDone']. If you want to index into computed sub-expressions, you need to enclose the expression in parentheses, e.g. (['a','b'])[0] (yields 'a').

  • CASE <expr> => <expr> CASE ... ENDCASE allows conditional evaluation; all case clauses are tried one by one until the first <expr> yields TRUE, in which case the corresponding second <expr> is used to compute the result. An error is yielded if no case matches.

    This means that FILTER <expr> has the same behavior as SELECT CASE <expr> => _ ENDCASE.

Precedence of the binary operators in increasing order is: or, xor, and, equality, ordering, additive, multiplicative, exponential. Indexing binds more strongly than negation.

Evaluation context

Each expression is evaluated as part of a processing step when applying this step to one particular input value. This value is available within the expression under the name _.

FROM 'myTag'
FILTER _.type = "started"
SELECT _.user_id

In this example the filter stage checks each incoming event for a type property with string value “started”. All matching events are passed on to the transformation step that extracts the value of the “user_id” property from the current event.