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] [, <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.
The id of an event is a string in the result, but compares as a number with a number, so e.id >= 1000 holds for the event with ID 1000 and every one after it. An id that comes out of a subquery is an ordinary string, so compare it with AS INT, such as e.id AS INT >= 1000.
Sorting, Grouping and Limiting¶
ORDER BY <field>sorts the result ascending (default) or descending. Several fields, separated by commas and each with a direction of its own, sort by the first, rows with an equal first value by the second, and so on, as inORDER BY e.type, e.time DESC. Ordering by theidof an event compares the ids as numbers, so10follows9, althoughidis a string in the result; cast to another type, such ase.id AS STRING, it compares as that type. Anidthat comes out of a subquery is an ordinary string, so order it withORDER BY e.id AS INTto compare it as a number. Rows whose values are all equal keep the order in which they were read when the last field sorts ascending, and come in the reverse order when it sorts descending, as though the order in which they were read followed as a last field in that direction, so that reversing every direction reverses the result exactly:ORDER BY e.time DESClists the events of one write, which share theirtime, latest first, and so doesORDER BY e.subject, e.time DESCwithin each subject.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' of type STRING to DATETIME: invalid format), and so does a value the target type cannot hold (e.g. cannot cast 'NaN' of type STRING to INTEGER: out of range). A cast to INT cuts off the fractional part, and keeps integers exact up to 1,000 digits, so '99999999999999999999' AS INT is 99999999999999999999.
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
Numbers are either integers, which are exact up to 1,000 digits, or decimals, which are 64-bit floating-point numbers. An operation on an integer and a decimal yields a decimal, and so does a division. Numbers compare by their value, whatever their type, so 1 == 1.0 is true.
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, end): Substring ofxfrom indexstartthrough indexend, both counted from0, soSUBSTRING("hello", 1, 3)returnsell. Withoutend, it runs to the end ofx.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, always as a float. For an even number of values, it is the mean of the two values in the middle.STDDEV(x): Sample standard deviation ofx, dividing by one less than the number of values. For a single value, it isNULL.VARIANCE(x): Sample variance ofx, dividing by one less than the number of values. For a single value, it isNULL.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.
Since comparing NULL is an error, a comparison of STDDEV(x) or VARIANCE(x) in a group that may hold a single value needs a guard, such as HAVING COUNT() > 1 AND STDDEV(x) > 1: AND stops at its first false operand.
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