EventQL Reference¶
This guide provides a comprehensive reference for EventQL, the query language used in EventSourcingDB to retrieve and transform event data. It describes the syntax, supported clauses, available functions, and supported types. This page is intended as a technical reference and does not explain usage patterns or best practices.
EventQL is case-insensitive. Keywords, function names, and types can be written in any casing.
Syntax Overview¶
An EventQL query consists of one or more FROM clauses followed by optional WHERE, ORDER BY, GROUP BY, HAVING, SKIP, and TOP clauses, and must end with a PROJECT INTO clause:
FROM <alias> IN <source>
[FROM <alias> IN <source>] ...
[WHERE <condition>]
[ORDER BY <field> [ASC | DESC]]
[GROUP BY <field> [HAVING <condition>]]
[SKIP <int-expression>]
[TOP <int-expression>]
PROJECT INTO [DISTINCT] <projection>
Comments can be added using // for single-line or /* ... */ for block comments.
Sources and Fields¶
You can query from the following sources:
events: The event stream.subjects: All known subjects.eventtypes: All known event types.- Subqueries: Enclosed in parentheses and returning a projected structure.
Each FROM clause defines an alias (FROM e IN events) that is used to refer to elements in expressions. Multiple FROM clauses create a cartesian product of their respective sources.
Fields available for events:
specversion: Version of the specification.id: Unique identifier of the event.time: Timestamp (date and time) the event was written.source: Source URI of the event.subject: Event subject.type: Event type.datacontenttype: Content type of thedatafield (alwaysapplication/json).data: JSON payload.predecessorhash: Hash of the predecessor event.hash: Hash of the event.traceparent: Distributed tracing parent context (ornullif not set).tracestate: Distributed tracing state (ornullif not set).signature: Ed25519 signature of the event (ornullif signatures are disabled).
Access to nested fields in data is supported using dot notation (e.g. e.data.count). If the field does not exist, the value resolves to null.
For the source subjects, each item is a string representing the subject identifier. There are no additional fields.
For the source eventtypes, each item is a string representing the event type. There are no additional fields.
Filtering with WHERE¶
The WHERE clause filters the input by evaluating a condition expression. Only results for which the condition evaluates to TRUE are included. There is no implicit boolean conversion — non-boolean values result in an error.
Operators:
- Comparison:
==,!=,<,>,<=,>= - Logical:
AND,OR,XOR,NOT - Containment:
array CONTAINS value - Grouping:
(...)
Conditions may include arithmetic, function calls, type conversion, and access to any fields in the current scope.
Sorting, Grouping and Limiting¶
ORDER BY <field>sorts the result ascending (default) or descending.GROUP BY <field>groups the results by a single field. All items in a group share the same value for that field. Aggregate functions will be evaluated separately for each group.HAVING <condition>filters entire groups based on aggregate conditions.SKIP <int-expression>skips a number of results after filtering, ordering, and grouping.TOP <int-expression>limits the number of returned items or groups.
There is no ungrouped output of group members (i.e. no array-style group flattening). Grouping is intended exclusively for aggregation; grouped items cannot be enumerated individually.
Projection with PROJECT INTO¶
The final result is defined using the PROJECT INTO clause. It accepts any expression, such as a structure using JSON-like syntax (which is a valid hash map literal in EventQL):
Examples:
The optional DISTINCT keyword removes duplicates. When using GROUP BY, duplicates are removed within each group.
Type Conversion¶
EventQL supports explicit type conversion using the AS keyword. Conversion behaves like parsing based on the expected format.
All date and time values must follow the RFC 3339 (with nanosecond precision, i.e. RFC3339Nano) format.
Supported types:
STRINGINTFLOAT64BOOLEANDATETIMEDATETIME
Invalid formats result in a runtime error (e.g. cannot cast 13:45:00 to DATETIME).
Expression Language¶
Expressions in EventQL include:
- Literals: Numbers, strings, booleans, null, timestamps.
- Object literals (hash maps):
{ key: value, ... } - Array literals:
[ value1, value2, ... ] - Field references: e.g.
e.data.price - Function calls: e.g.
LEN(e.data.items) - Type conversions: e.g.
"42" AS INT - Conditional expressions:
IF(condition, then, else) - Operators: Comparison, logical, arithmetic
Built-in Functions¶
Mathematical Functions¶
ABS(x): Absolute value of a number.CEIL(x): Smallest integer not less thanx.FLOOR(x): Largest integer not greater thanx.ROUND(x): Rounded value ofx.COS(x),SIN(x),TAN(x): Trigonometric functions.EXP(x): Exponential function ofx.POW(x, y):xraised to the power ofy.SQRT(x): Square root ofx.RAND(): Pseudorandom number between 0 and 1.PI(): Value of π.
String Functions¶
LOWER(x): Lowercase string.UPPER(x): Uppercase string.TRIM(x): Trim leading and trailing whitespace.LTRIM(x): Trim leading whitespace.RTRIM(x): Trim trailing whitespace.LEN(x): Length of a string, array, or hash map.INSTR(x, y): Index of substringyin stringx.SUBSTRING(x, start, length): Substring fromxstarting atstartwith givenlength.REPLACE(x, y, z): Replace all occurrences ofyinxwithz.STARTSWITH(x, y): Checks ifxstarts withy.ENDSWITH(x, y): Checks ifxends withy.
Date and Time Functions¶
NOW(): Current timestamp.YEAR(x): Year part of aDATEorDATETIME.MONTH(x): Month part of aDATEorDATETIME.DAY(x): Day part of aDATEorDATETIME.HOUR(x): Hour part of aTIMEorDATETIME.MINUTE(x): Minute part of aTIMEorDATETIME.SECOND(x): Second part of aTIMEorDATETIME.WEEKDAY(x): Weekday (0 = Sunday) of aDATEorDATETIME.
Conditional Functions¶
IF(condition, then, else): Returnsthenif condition is true, otherwiseelse.
Aggregation Functions¶
When aggregation functions are used, the result will contain a single item, unless grouped. Only aggregate functions and constant expressions are allowed in the projection. Mixing aggregate functions with source-bound fields (e.g. e.id) is not allowed.
Examples:
// Allowed
PROJECT INTO {
sum: SUM(e.data.count),
label: "everything summed",
randomvalue: RAND()
}
// Not allowed
PROJECT INTO {
sum: SUM(e.data.count),
id: e.id
}
Available functions:
COUNT(): Counts all input items.COUNT(x): Counts only inputs wherexevaluates toTRUE.SUM(x): Sum of all values ofx.AVG(x): Arithmetic mean of values ofx.MIN(x): Minimum value ofx.MAX(x): Maximum value ofx.MEDIAN(x): Median value ofx.STDDEV(x): Standard deviation ofx.VARIANCE(x): Statistical variance ofx.UNIQUE(x): Ensuresxhas the same value across all inputs. For example the field that is used by theGROUP BYexpression, which is always the same within each group.
Example Queries¶
// Select the 100 most recent events
FROM e IN events
ORDER BY e.time DESC
TOP 100
PROJECT INTO e
// Select events of a specific type
FROM e IN events
WHERE e.type == "io.eventsourcingdb.library.book-acquired"
PROJECT INTO { id: e.id, book: e.data.title }
// Count all events of a certain subject
FROM e IN events
WHERE e.subject == "/books/42"
PROJECT INTO { total: COUNT() }
// Select events with nested data
FROM e IN events
WHERE e.data.price > 20
PROJECT INTO { id: e.id, price: e.data.price }
// Group by event type and count per group
FROM e IN events
GROUP BY e.type
PROJECT INTO { type: UNIQUE(e.type), count: COUNT() }
// Use a subquery to enrich events
FROM e IN (
FROM e IN events
WHERE e.type == "io.eventsourcingdb.library.book-acquired"
PROJECT INTO { orderId: e.id, value: e.data.total }
)
WHERE e.value > 100
PROJECT INTO e