Definition
The full definition tree of an event filter — query blocks, projection, column, record and related table filters, time window and aggregation.
What this page is
The definition of an event filter is the tree that describes which events it selects. It is identical in every endpoint that receives it or returns it, so it is documented only once here:
| Endpoint | Where the definition appears |
|---|---|
POST /event-criterias | In the body, under queryBlocks |
PATCH /event-criterias/:id | In the body, under queryBlocks (optional) |
GET /event-criterias/:id | In the response, under queryBlocks |
POST /generate-sql | In the body, under queryBlocks |
POST /validate-from-body | In the body, under queryBlocks |
POST /preview-from-body | In the body, under queryBlocks |
The table and column names that appear in the examples come from the catalog, which is the source of what can be filtered.
queryBlocks[] — query block
Minimum 1 element. Each block (QueryBlockDto) is a complete SQL statement; blocks
are combined with each other via setOperator.
| Field | Type | Required | Description |
|---|---|---|---|
order | number (integer) | Yes | Block position; integer ≥ 0. Unique within the filter. |
setOperator | SetOperatorEnum | No | Combines this block with the previous one. Absent in the order = 0 block; required in the rest. |
from | FromDto | Yes | Data source: main table + joins. |
select | SelectDto | Yes | Projection (columns and/or aggregations). |
timeWindow | TimeWindowDto | No | Block time window (discriminated union by timeOperator). |
where | WhereDto | Yes | Filter tree. Required even if it is an empty group. |
groupBy | ColumnRefDto[] | No | Grouping columns. |
having | HavingDto | No | Filter over aggregates. |
An event filter is not composed with other filters: there is no composedWith. A
block's setOperator combines blocks within the same filter, and it is the only
level of set operation there is.
ColumnRefDto — column reference
Used throughout the tree (select, column filters, group by, joins, aggregates, time window):
| Field | Type | Required | Description |
|---|---|---|---|
table | string | Yes | Catalog table. Non-empty. |
column | string | Yes | Column. Non-empty. Can be a non-repeated STRUCT path (e.g. "pricing.final_amount"). |
Every column is always qualified with its table to avoid ambiguity. The exception is in
the relative sub-trees —the inside of a record filter or of a related table
filter—, where the operand is a bare field with no table (see
Relative sub-tree).
from — data source (FromDto)
| Field | Type | Required | Description |
|---|---|---|---|
table | string | Yes | Main catalog table. Non-empty. |
joins | JoinDto[] | No | Joins with other tables. Default []. |
Each JoinDto:
| Field | Type | Required | Description |
|---|---|---|---|
type | JoinTypeEnum | Yes | INNER | LEFT | RIGHT | FULL. |
table | string | Yes | Table to join (from the catalog). Non-empty. |
on | JoinPredicateDto[] | Yes | Join conditions. |
Each JoinPredicateDto is a pair of columns: { left: ColumnRefDto, right: ColumnRefDto }.
Both sides must be scalar columns.
Only the tables returned by
GET /tables can take part in the from.
The target table of a semi-join is not a catalog table: it does not enter the from
nor is it the target of a join, and it is only reached with a
related table filter.
select — projection (SelectDto)
| Field | Type | Required | Description |
|---|---|---|---|
distinct | boolean | Yes | Applies SELECT DISTINCT. |
items | (SelectColumnItem | SelectAggregateItem)[] | Yes | Non-empty. Discriminated union by the kind field. |
The items are discriminated by kind:
-
Column —
kind: "column":Field Type Required Description kind"column"Yes Discriminator. columnColumnRefDtoYes Projected column. Must be scalar. aliasstring No Must match ^[A-Za-z_][A-Za-z0-9_]*$. -
Aggregate —
kind: "aggregate":Field Type Required Description kind"aggregate"Yes Discriminator. aggregateAggregateDtoYes Aggregation expression. aliasstring Yes Here the alias is required. Same identifier regex.
AggregateDto:
| Field | Type | Required | Description |
|---|---|---|---|
aggregationOperator | AggregationOperatorEnum | Yes | COUNT | SUM | AVG | MIN | MAX. |
operand | object | Yes | What is aggregated. Discriminated union on kind: column, star or arithmetic (see below). |
distinct | boolean | No | Applies DISTINCT within the aggregate. COUNT(DISTINCT *) is not allowed. |
operand — what is aggregated — is discriminated by kind:
kind | Additional fields | Meaning |
|---|---|---|
column | column: ColumnRefDto | A catalog column. |
star | (none) | *: counts rows. Only valid with COUNT. |
arithmetic | arithmeticOperator: ArithmeticOperatorEnum, left, right | Binary expression. Each side is another operand: a column or a nested arithmetic (never star). |
ArithmeticOperatorEnum: ADD | SUBTRACT | MULTIPLY | SAFE_DIVIDE (a division that yields
NULL when the denominator is zero instead of failing the query). The columns of an arithmetic
expression must be numeric (INT64, NUMERIC, BIGNUMERIC, FLOAT64), and expressions nest up to
3 levels of arithmetic, counting the outermost one.
A RECORD REPEATED column (attributes, items) cannot be projected, grouped,
aggregated or used in a join's ON: it is not compared by itself. Attempting it responds
400 with La columna "<tabla>.<columna>" es una columna repetida y no puede usarse en <contexto>.
Its place is the record filter.
where — filter tree (WhereDto)
The where is a recursive tree. The root node is always a group, and its leaves come
in three types:
kind | What it filters |
|---|---|
group | Not a leaf: it groups children with AND / OR. Groups can be nested. |
predicate | A scalar column of a table in the from. |
recordPredicate | A RECORD REPEATED column: there is ≥1 element that satisfies the sub-filter. |
semiJoinPredicate | A semi-join from the catalog: there is ≥1 match in the related table. |
Group — kind: "group"
| Field | Type | Required | Description |
|---|---|---|---|
kind | "group" | Yes | Discriminator. |
logicalOperator | LogicalOperatorEnum (AND | OR) | Yes | Connective between the children. |
children | (Group | Leaf)[] | Yes | Group children; they can be nested groups. |
logicalOperator is always required in a group, but it only takes effect with 2 or
more children. With fewer than 2 children its value is irrelevant (it generates the same
SQL), but it must be sent anyway. A where with no filters is expressed as an empty
group: { "kind": "group", "logicalOperator": "AND", "children": [] }.
Column leaf — kind: "predicate"
Compares a scalar column qualified by its table.
| Field | Type | Required | Description |
|---|---|---|---|
kind | "predicate" | Yes | Discriminator. |
column | ColumnRefDto | Yes | Predicate column. |
comparisonOperator | ComparisonOperatorEnum | Yes | Comparison operator. |
value | PredicateValue | Depends | Its shape depends on the operator. |
{
"kind": "predicate",
"column": { "table": "event_transactions", "column": "status" },
"comparisonOperator": "EQUALS",
"value": "Facturada"
}Record leaf — kind: "recordPredicate"
Filters by a RECORD REPEATED column (an array of structs, such as attributes or
items). It means there is at least one element of the array that satisfies the sub-filter.
| Field | Type | Required | Description |
|---|---|---|---|
kind | "recordPredicate" | Yes | Discriminator. |
column | ColumnRefDto | Yes | RECORD REPEATED column of a table in the from. |
subWhere | Relative sub-tree | Yes | Conditions over the element's sub-fields. It cannot be left empty. |
{
"kind": "recordPredicate",
"column": { "table": "event_transactions", "column": "attributes" },
"subWhere": {
"kind": "group",
"logicalOperator": "AND",
"children": [
{ "kind": "predicate", "field": "key", "comparisonOperator": "EQUALS", "value": "documento_tipo" },
{ "kind": "predicate", "field": "value", "comparisonOperator": "EQUALS", "value": "factura" }
]
}
}The subWhere conditions are evaluated over one and the same element of the array,
not over the whole array. The example above selects events that have an attribute whose
key is documento_tipo and whose value is factura — not events that have one
attribute with that key and another, different one, with that value.
Related table leaf — kind: "semiJoinPredicate"
Filters events by the existence of a match in another table, following a semi-join that the catalog declares. The path between the two tables is resolved by the catalog: the request only names the owner table and the semi-join.
| Field | Type | Required | Description |
|---|---|---|---|
kind | "semiJoinPredicate" | Yes | Discriminator. |
table | string | Yes | Table that owns the semi-join. It must take part in the block's from. |
semiJoin | string | Yes | Name of the semi-join that this table declares in the catalog. |
subWhere | Relative sub-tree | Yes | Conditions over the related table's fields. It cannot be left empty. |
{
"kind": "semiJoinPredicate",
"table": "event_transactions",
"semiJoin": "products",
"subWhere": {
"kind": "group",
"logicalOperator": "AND",
"children": [
{
"kind": "predicate",
"field": "categorization.category_name",
"comparisonOperator": "IN",
"value": ["Food Service", "HORECA"]
}
]
}
}A semi-join cannot be nested inside another one: the subWhere of a related table
leaf accepts scalar and record leaves, but not another semiJoinPredicate
leaf.
Relative sub-tree
The subWhere of a record filter and that of a related table filter have the same
shape: an AND/OR group whose leaves are resolved against the fields that the scope
declares —the record's sub-fields, or the related table's fields.
There is a single difference with the top-level tree, and it is the most common source of error:
Inside a relative sub-tree the operands carry no table: you write
"field": "item_id", not "column": { "table": "items", "column": "item_id" }. The leaf
uses the field key (a string), not column.
Leaves accepted inside a relative sub-tree:
- Scalar leaf —
{ "kind": "predicate", "field": string, "comparisonOperator": …, "value"?: … }. Thefieldmust be a scalar sub-field of the scope. - Nested record —
{ "kind": "recordPredicate", "field": string, "subWhere": … }. A record inside another record (for exampleitems[].attributes[]); it nests with no declared limit, as long as the catalog declares each level. - Group — the same group shape, to combine the previous leaves with
AND/OR.
Shape of value by comparisonOperator
| Operators | Shape of value | Example |
|---|---|---|
IS_NULL, IS_NOT_NULL | No value (omitted) | (omitted) |
IN, NOT_IN | Non-empty list ScalarValue[] | ["Food Service", "HORECA"] |
BETWEEN | Pair [min, max] | [10, 20] |
The rest (EQUALS, NOT_EQUALS, GREATER_THAN, LESS_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL, CONTAINS, NOT_CONTAINS, STARTS_WITH, ENDS_WITH) | Single scalar | "Facturada" · 42 |
ScalarValue = string | number | boolean. The rule holds equally for column
leaves and for the scalar leaves of a relative sub-tree.
Operators accepted by column type
Each operator must also be compatible with the column type that the catalog declares.
An operator outside this table responds 400 with
Operator <op> is not allowed for <type> operands.
| Column type | Accepted operators |
|---|---|
STRING | EQUALS · NOT_EQUALS · CONTAINS · NOT_CONTAINS · STARTS_WITH · ENDS_WITH · IN · NOT_IN · IS_NULL · IS_NOT_NULL |
BOOLEAN | EQUALS · NOT_EQUALS · IS_NULL · IS_NOT_NULL |
FLOAT64, INT64, NUMERIC, BIGNUMERIC | EQUALS · NOT_EQUALS · GREATER_THAN · LESS_THAN · GREATER_THAN_OR_EQUAL · LESS_THAN_OR_EQUAL · BETWEEN · IN · NOT_IN · IS_NULL · IS_NOT_NULL |
TIMESTAMP | EQUALS · NOT_EQUALS · GREATER_THAN · LESS_THAN · GREATER_THAN_OR_EQUAL · LESS_THAN_OR_EQUAL · BETWEEN · IS_NULL · IS_NOT_NULL |
Filtering by date range does not go through these operators: it is covered by the
timeWindow field.
groupBy — grouping columns
Optional list of ColumnRefDto[]. When the block aggregates (declares groupBy or has an
aggregate in the select), the groupBy must match exactly the non-aggregated
columns of the select.
having — filter over aggregates (HavingDto)
Same recursive structure as the where —nested AND/OR groups and the same value
rule per operator—, but the leaf operates over an aggregate, not over a column:
- Group — identical to the one in the
where. - Leaf —
{ "kind": "predicate", "aggregate": AggregateDto, "comparisonOperator": …, "value"?: … }.
having requires an aggregation context: the block must declare groupBy or have an
aggregate in the select. Without that it responds 400 with
HAVING requires an aggregation context (GROUP BY or an aggregate in the SELECT).
timeWindow — time window (TimeWindowDto)
Discriminated union by timeOperator. Constrains the block to a time period over a
TIMESTAMP column from the catalog (reference). It is a sibling field of the where,
not a node of the filter tree.
timeOperator | Additional fields |
|---|---|
ALL_TIME | (none) — no time constraint. |
IN_THE_LAST | reference: ColumnRefDto, unit: TimeUnitEnum, value: integer > 0 |
SINCE | reference: ColumnRefDto, from: string (YYYY-MM-DD) |
BETWEEN | reference: ColumnRefDto, from: string (YYYY-MM-DD), to: string (YYYY-MM-DD) |
TimeUnitEnum: DAYS | WEEKS | MONTHS | YEARS.
Semantics of each operator:
ALL_TIME— no window; considers the entire history.IN_THE_LAST— rolling window relative to "now": the lastvalueunit(e.g. last30 DAYS). Recomputed on each run.SINCE— from the civil datefrom(inclusive) onward.BETWEEN— betweenfromandto, both civil dates inclusive of their full day.
IN_THE_FIRST is a valid member of TimeOperatorEnum, but it is not supported in
this resource: it measures the life of a subject and an event filter does not have one.
Sending it responds 400 with
timeWindow IN_THE_FIRST is not supported: the catalog does not declare a lifetime origin.
Civil dates YYYY-MM-DD (SINCE, BETWEEN) are resolved to the start of the day in
the tenant's timezone. The timezone detail is internal; it is enough to send the civil
date.
Validation Rules
The definition goes through two layers of validation, both returning 400 Bad Request:
- DTO validation (structure, types, enums, required fields).
messageis an array of strings and unknown properties are rejected. - Business validation (the definition must generate valid SQL that is coherent against
the catalog).
messageis a single string with the message of the violated rule, and it stops at the first violated invariant.
Business rules verified, with the exact message from the API:
Catalog and references
| Rule | Message |
|---|---|
| The table must belong to the catalog. | Table "<t>" is not in the catalog |
Each (table, column) must belong to the catalog. | Column "<t>.<c>" is not in the catalog |
Every referenced column must belong to a table in the from. | Column "<t>.<c>" references a table that is not in the query block FROM |
| A repeated column cannot be used outside a record filter. | La columna "<t>.<c>" es una columna repetida y no puede usarse en <contexto> |
| A leaf of the tree must declare its operand. | Una hoja del filtro no declara una columna |
Projection and aggregation
| Rule | Message |
|---|---|
The star operand (*) is only valid with COUNT. | '*' is only valid with COUNT, not <op> |
COUNT(DISTINCT *) is not allowed. | COUNT(DISTINCT *) is not allowed |
star cannot be a side of an arithmetic expression. | '*' sólo vale como operando directo de COUNT, no como lado de una expresión aritmética |
| The columns of an arithmetic expression must be numeric. | El operando "<t>.<c>" es de tipo <type>; una expresión aritmética sólo admite operandos numéricos (INT64, NUMERIC, BIGNUMERIC, FLOAT64) |
| An arithmetic expression nests at most 3 levels. | La expresión aritmética supera la profundidad máxima de 3 niveles |
When aggregating, every non-aggregated column of the select must be in the groupBy. | Column "<t>.<c>" must appear in GROUP BY when the operation aggregates |
Every column in the groupBy must be a non-aggregated column of the select. | Column "<t>.<c>" in GROUP BY must be a non-aggregated column of the SELECT |
having requires an aggregation context. | HAVING requires an aggregation context (GROUP BY or an aggregate in the SELECT) |
Query blocks
| Rule | Message |
|---|---|
| There must be at least one block. | A criteria must declare at least one operation (query block) |
The order values are unique. | Two operations declare the same order=<n>: each operation order must be unique |
The order = 0 block must exist. | Missing the first operation: one operation must declare order=0 |
The first block does not carry a setOperator. | First operation (order=0) must not have a setOperator |
Every subsequent block must carry a setOperator. | Operation with order=<n> must have a setOperator |
Operators and values
| Rule | Message |
|---|---|
| The operator must be compatible with the column/aggregate type. | Operator <op> is not allowed for <type> operands |
Scalar operator with a missing value or a list. | Operator <op> requires a single value |
IN/NOT_IN with a non-list or empty value. | Operator <op> requires a non-empty list of values |
BETWEEN without exactly two values. | Operator <op> requires exactly two values |
Record and related table filters
| Rule | Message |
|---|---|
| A record filter requires a repeated column. | La columna "<t>.<c>" no es una columna repetida: no admite un filtro de registro |
| A column filter requires a scalar column. | La columna "<t>.<c>" es una columna repetida: no admite un filtro de columna |
| The sub-field must exist in the record. | El sub-campo "<f>" no existe en el registro |
| The sub-field must exist in the related table. | El sub-campo "<f>" no existe en la tabla relacionada "<t>" |
| A scalar leaf cannot point at a repeated sub-field. | El sub-campo "<f>" es una columna repetida: usá un filtro de registro anidado |
| A nested record requires a repeated sub-field. | El sub-campo "<f>" no es una columna repetida |
| The sub-tree of a record filter cannot be left empty. | Un filtro de registro no puede quedar sin condiciones |
| The sub-tree of a related table filter cannot be left empty. | Un filtro sobre la tabla relacionada "<t>" no puede quedar sin condiciones |
The table that owns the semi-join must take part in the from. | El semi-join "<s>" se declara sobre la tabla "<t>", que no participa del FROM del bloque |
| The semi-join must be declared by that table in the catalog. | El semi-join "<s>" no está declarado en la tabla "<t>" |
Time window
| Rule | Message |
|---|---|
The reference must be in the catalog. | timeWindow reference "<t>.<c>" is not in the catalog |
The reference must be a TIMESTAMP column. | timeWindow reference "<t>.<c>" must be a TIMESTAMP column |
The reference cannot be a repeated column. | La referencia temporal "<t>.<c>" es una columna repetida y no puede anclar una ventana de tiempo |
value must be a positive integer. | timeWindow value must be a positive integer |
unit must be a member of TimeUnitEnum. | timeWindow unit "<u>" is not a valid time unit |
In BETWEEN, from must be on or before to. | timeWindow BETWEEN requires "from" to be on or before "to" |
| Dates must be valid calendar dates. | timeWindow date "<d>" is not a valid calendar date |
IN_THE_FIRST is not supported. | timeWindow IN_THE_FIRST is not supported: the catalog does not declare a lifetime origin |