This page is the language syntax and semantics reference for PerfettoSQL pipelines.
Warning: Pipe syntax is experimental and unstable. Do not rely on it in dashboards, scripts, or production queries. Anything can and will break without notice. Outside the PerfettoSQL standard library, enable it on a connection with
PERFETTO PRAGMA pipelines = 1;.
Related pages:
PERFETTO PRAGMA pipelinesControls whether pipe syntax is permitted on the current connection outside the standard library:
PERFETTO PRAGMA pipelines = 1; -- Enable pipelines PERFETTO PRAGMA pipelines = 0; -- Disable pipelines
The right-hand side must evaluate to a whole number. Unknown pragma names produce an error.
A pipeline is valid in two statement positions:
-- 1. Standalone query <pipeline>; -- 2. Materialized Perfetto table CREATE [OR REPLACE] PERFETTO TABLE <table_name> [(<schema>)] AS <pipeline>;
A <pipeline> consists of a source, either FROM or INTERVAL INTERSECTION OF, followed by zero or more |> stages:
<source> [|> <stage_1>] [|> <stage_2>] ...
Note: The pipe operator
|>must be written with no whitespace between|and>. The pipeline keywordsTREE,ACCUMULATE,UP,DOWN,INTERVAL,INTERSECTION,FLATTEN,AGGREGATE,PER, andEXTENDare non-reserved and remain valid as normal identifiers in standard SQL statements.
FROMStarts a pipeline from a single relation:
FROM [<schema>.]<table_name> [[AS] <alias>] FROM (<select_stmt>) [[AS] <alias>]
<table_name> may be any table or view, or a macro invocation that expands to a table or parenthesized subquery.(<select_stmt>) may be any valid SQL SELECT statement enclosed in parentheses.<alias> is given, columns of the source can be qualified with <alias>.<col>. When the source is a table name, <alias> replaces <table_name> as the qualifier.<table_name>, columns can be qualified with <table_name>.<col>.(<select_stmt>), its columns have no table qualifier unless a later |> AS <alias> stage binds one.INTERVAL INTERSECTION OFStarts a pipeline with the overlapping time regions across two or more relations:
INTERVAL INTERSECTION OF ( <source_1> [AS] <alias_1>, <source_2> [AS] <alias_2> [, <source_N> [AS] <alias_N> ...] ) [PER <col_1> [, <col_2> ...]]
[AS] <alias>.ts and dur columns, and every column listed in PER must exist in every operand.For each overlapping region [ts, ts + dur) shared by all operands within a matching PER key group, one row is produced with the following columns:
ts and dur: 64-bit integer columns holding the intersected region's start and duration. These are the only ts and dur columns visible to unqualified column lookups.PER key columns are visible both unqualified, such as cpu, and qualified, such as <alias_1>.cpu.ts and dur, as well as subsequent operands’ copies of PER columns. They can only be referenced via <alias_i>.<col> or expanded via * or <alias_i>.*.[ts, ts + dur). Two intervals touching at their endpoints, such as [0, 10) and [10, 20), do not intersect.dur = 0 is a point at instant ts. It intersects an interval [a, b) when a <= ts < b, and intersects another point when both sit at the same instant ts, producing a region with dur = 0.ts IS NULL or dur IS NULL covers no time and is skipped.ts < 0 or dur < 0 fails with the error ts or dur is below zero.NULL in PER columns: Two rows with NULL in a PER column agree on that column, matching GROUP BY semantics.Every column of any pipeline source must have a valid identifier name matching ^[a-zA-Z_][a-zA-Z0-9_]*$ and must be distinct within that source, even if a later stage drops the column:
1 + 1 or max(ts) are rejected with: expected every column to have a valid name, but '<expr>' is not one: give it one with AS.expected distinct column names, but there are two named '<name>'.INTERVAL FLATTENSplits overlapping intervals into segments and aggregates the rows active in each segment:
|> INTERVAL FLATTEN [PER <col_1> [, <col_2> ...]] AGGREGATE <aggregate> AS <out_1> [, <aggregate> AS <out_2> ...]
ts and dur in the current row. Both must be integer columns. Input need not be sorted; the pipeline arranges rows by timestamp within each group.PER values is flattened independently. Without PER, all rows belong to one group. NULL keys group together, as in SQL GROUP BY.COUNT(*) and SUM([<qualifier>.]<column>). Each needs an AS name. SUM accepts integer columns (ID, UINT32, INT32, or LONG), ignores NULL values, and returns NULL if all contributing values are NULL. Expressions, DISTINCT, FILTER, OVER, and other aggregate functions are unsupported.ts, dur, the PER columns in their listed order, and the named aggregates in their listed order. ts, dur, and aggregates are LONG columns. Other input columns and all table aliases are removed. Output names must be distinct.[ts, ts + dur). Output splits at input start and end timestamps, with aggregates over the rows covering each segment. Gaps with no active rows produce no output. Adjacent segments are not merged merely because their aggregate values are equal.dur = 0 at the same timestamp produce one additional zero-duration segment per group. Its aggregates include all points there and all positive-duration intervals covering that instant. Intervals ending there are excluded; intervals starting there are included. Points do not contribute to positive-duration segments.ts IS NULL or dur IS NULL are skipped.ts or dur values cause an error. Filter out unfinished trace slices (dur = -1) in the source subquery if needed.For a worked example, see Flattening Overlapping Intervals.
TREE ACCUMULATEAccumulates sums up or down a tree defined by id and parent_id:
|> TREE ACCUMULATE UP|DOWN SUM([<qualifier>.]<col_1>) AS <out_1> [, SUM([<qualifier>.]<col_2>) AS <out_2> ...]
id and parent_id in the current row. Root nodes have parent_id IS NULL.UP: Bottom-up fold. Each node's aggregate is the sum of its own value and the values of all its descendants. Output rows are emitted child-first.DOWN: Top-down fold. Each node's aggregate is the sum of its own value and the values of all its ancestors. Output rows are emitted parent-first.SUM(<column_ref>) is currently supported. DISTINCT, FILTER, OVER, *, non-column expressions, and other aggregate functions are rejected.ID, UINT32, INT32, or LONG. NULL values contribute 0.<out_1>, <out_2>, ... as LONG columns.ORDER BYSorts the rows:
|> ORDER BY <expr_1> [ASC | DESC] [, <expr_2> [ASC | DESC] ...]
ASC. A key parses as an expression, but only a column, optionally qualified, is supported yet. A bitwise OR (|) must be in parentheses, since |> starts the next stage.NULL values: Sort as in SQLite: first when ascending, last when descending.TREE ACCUMULATE or INTERVAL FLATTEN stage arranges rows as it needs, so put ORDER BY last to order a pipeline's result.SELECTReplaces the current row with the specified items and clears all table aliases:
|> SELECT <select_item> [, <select_item> ...]
Each <select_item> is one of:
[<qualifier>.]<col> [[AS] <alias>][<qualifier>.]* [EXCEPT (<col_1> [, <col_2> ...])] [REPLACE (<replace_item> [, ...])] where each <replace_item> is [<qualifier>.]<source_col> AS <target_col>. AS is required in REPLACE.Star expansion rules:
* starts with all columns in the current row, including qualified-only operand columns after INTERVAL INTERSECTION OF. <qualifier>.* starts with the columns captured when <qualifier> was bound.EXCEPT (<col>, ...) removes every column matching any listed bare name case-insensitively. Every listed name must match at least one column, no name may be listed twice, and at least one column must remain after EXCEPT.REPLACE (<source_col> AS <target_col>, ...) resolves <source_col> against the input row or aliases before the stage and replaces the value of <target_col> in the star's expansion while keeping <target_col>'s position and name. <target_col> must match a single column remaining in the star after EXCEPT.* or <qualifier>.* can be referenced without a qualifier in subsequent stages.EXTENDAppends columns to the end of the current row while keeping all existing columns and aliases:
|> EXTEND <extend_item> [, <extend_item> ...]
SELECT, except that a bare * is not allowed. Use <qualifier>.* to append all columns of a table alias.EXTEND resolve against the row before the stage, so an item cannot reference another column added in the same EXTEND stage.EXTEND are not added to any existing table aliases.DROPRemoves columns from the current row by bare name:
|> DROP <col_1> [, <col_2> ...]
DROP.RENAMERenames columns in the current row in place:
|> RENAME <old_1> [AS] <new_1> [, <old_2> [AS] <new_2> ...]
<old_i> must be a bare column name that resolves unambiguously in the current row, and may not be listed twice in the same RENAME.|> RENAME x AS y, y AS x swaps x and y.SETReplaces the values of existing columns in the current row in place:
|> SET <target_1> = [<qualifier>.]<source_1> [, <target_2> = [<qualifier>.]<source_2> ...]
<target_i> must be a bare column name that resolves unambiguously in the current row, and may not be listed twice in the same SET.<source_i> is resolved against the row and aliases before the stage, so |> SET x = y, y = x swaps the values of x and y.SET values, except that any table alias whose name matches <target_i> is removed.ASReplaces all existing table aliases with a single new alias bound to a snapshot of the current row:
|> AS <alias>
After |> AS u, earlier table aliases are no longer in scope, and u.<col> and u.* reflect the row as of the AS stage.
|> EXTEND x or |> RENAME x AS y when y already exists. Duplicate names only cause an error if a later stage references the ambiguous name without a table qualifier. You can also drop or exclude all copies at once via DROP y or * EXCEPT (y)."...", `...`, or [...]. Inside "..." and `...`, doubling the quote character escapes a literal quote, and "" spells the empty column name.