Skip to content

How a query is put together ​

A query is some let names, one source, and a pipeline of operators that shape the rows the source produces.

Once you can see those three parts in any query, the rest of the handbook is detail. This page names them, says what a row is and where its columns come from, and shows how names are chosen when you do not choose them yourself.

The three parts ​

cql
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;   // 1. names

from stETH at latest-21600..latest every 7200 as s                  // 2. the source
| extend pooled = s.getTotalPooledEther()                            // 3. the pipeline
| where pooled > 0
| project { block: $block, pooled: format(pooled, 18) }

Open in workbench →

Names come first. let binds a name to an address, an ABI, or a plain value, and ends with a semicolon. A let cannot read anything from a row — it is settled before the first row exists — so let x = s.getTotalPooledEther(); is refused with a note to use extend instead.

The source starts with from and says what to read: which contracts, whether you want their state or their events, at which blocks, and what to call the result. Everything after the contract is optional. There is exactly one source per pipeline; a second contract set is another source inside a join or a union.

The pipeline is zero or more operators, each starting with |. Rows flow top to bottom: every operator takes the rows the previous one produced and passes on what it keeps, adds or combines.

What a row is ​

A state row is one contract at one block. from stETH at latest is one row; the same with at latest-21600..latest every 7200 is one row per sampled block; from [a, b] doubles that. Every call you make on the alias — s.getTotalPooledEther() — is answered at that row's block, which is what makes a range of state rows a time series.

An event row is one log. from stETH events TokenRebased gives one row per TokenRebased the contract emitted in the range — one a day, when the oracle reports — with the event's arguments as columns. A call on an event row's alias is still allowed and is answered at the block the event was in.

Both kinds carry system columns: $block, $chain, $address and $timestamp on every row, and $tx, $logIndex, $event and a few more on event rows.

Where columns come from ​

Columns come from three places, and you can tell which by how they are written:

  • A system column starts with $: $block, $timestamp.
  • A field of the source is written through its alias: e.postTotalEther, t.value. On a state row the alias has no fields of its own, only functions to call: s.getTotalPooledEther().
  • A column you made with extend, project or summarize: pooled, total.

An unqualified name is looked up in that order — alias first, then a column, then a let. After a join, a column both sides have is kept from both, the right one renamed with a number: with extend treasury = … on each side, a bare treasury is the left side's and treasury1 the right side's. A field of the right source can also be named through its alias, so b.value is the same column as value1; a column you made has no alias to go through. Operators has the whole rule.

How columns get their names ​

When you write project { pooled: format(pooled, 18) } the name is yours. When you do not name a column, CQL derives one, and the rule is short: a path keeps its last segment, a call keeps the function name, an aggregate prefixes the function.

You writeThe column is called
t.fromfrom
s.$address$address
s.getTotalPooledEther()getTotalPooledEther
count()count_
sum(t.value)sum_value

Anything else — t.value * 2, say — needs a name, and CQL says so rather than inventing Column1. To name a column, write name: expr inside project { … }, or name = expr after extend, inside summarize and after by. Do not write expr as name: in an expression, as is a type cast, and t.value as amount is refused.

The alias ​

as s names the source so you can reach it from the pipeline. Everything you read from the contract goes through that name: s.getTotalPooledEther() calls a function, s.$address reads which contract this row is, s.$storage[0] reads raw storage. The alias is not a column; project { s } is an error.

See also ​