SQL tables

Embed
Preview of the card
<a href="https://forumulate.com/p/watchtower/docs/sql-tables"><img src="https://forumulate.com/api/public/orgs/watchtower/spaces/docs/pages/90cf1edd-9065-4e3b-8679-c1c16b1039bd/social-card.svg?v=3e7ae33eec8b" alt="SQL tables" width="600" height="315"></a>

POST /v1/projects/{p}/query/sql reads virtual tables only. Each name becomes a subquery over the real ClickHouse table with project_id = {p:UUID} first, then the request time range. GET /v1/projects/{p}/sql/tables lists every table with its columns and types (the allow-list).

Table

Rows

Notes

logs, spans, errors, check_results, metric_samples, flows

as before

SELECT * over the allow-listed columns

error_events

error events

same data as errors

rum_events

one per browser event

type, name, value, route, browser, linked trace_id

rum_sessions

one per session

vitals (lcp_ms, inp_ms, cls), rage and dead clicks, trace_ids

replays

replay session metadata

no payload: chunk bytes stay in object storage; deleted replays are hidden

profile_functions

hourly per function

self and total per service, version, sample type

service_red_1m

per-minute RED rollup

count, errors, latency bucket counts le_*; rollup parts, so sum() them

llm_calls

one per GenAI span

tokens, cost, model, operation, outcome

The new tables select only their listed columns: project_id, retention_days and aggregate-state columns are never readable.

Join across signals through ids, for example sessions to backend spans:

SELECT r.session_id, count() AS spans
FROM rum_sessions AS r
JOIN rum_events AS e ON r.session_id = e.session_id
JOIN spans AS s ON e.trace_id = s.trace_id
GROUP BY r.session_id

Limits: max_rows defaults to 10,000 and may go up to 100,000. Requests above 10,000 count against a per-caller budget of 1,000,000 such rows per hour (429 when spent). A result over 64 MiB fails with "result too large".

Known limits: rum_sessions, replays and rum_events are ReplacingMergeTree tables and are read without FINAL, so a row may appear twice until ClickHouse merges it; use argMax(col, last_seen) grouped by session_id when exact one-row-per-session matters.

Updated