Documentation
¶
Overview ¶
Package sql is a small, read-only SQL dialect over the event stream.
It exists so an agent can answer a question nobody pre-built a report for. Every other report in this codebase answers one shape of question; this answers the rest, which is the difference between "we support the twenty things we shipped" and "ask anything".
Three deliberate constraints:
- READ ONLY BY CONSTRUCTION. There is no INSERT, UPDATE, DELETE or DDL in the grammar, so there is nothing to escape from and no injection surface. A write is a parse error, not a permission check that could be wrong.
- STREAMING. Aggregation happens as events go past, so memory is O(distinct groups) rather than O(events). A GROUP BY over ten million events holds a few thousand accumulators. This is the same lesson as the dashboard render: never hold history to compute over it.
- NO DEPENDENCIES. The binary has none, which is a claim worth more than the weeks this cost to hand-write. Embedding SQLite would have been faster to build and would have materialized every row into a table first.
The dialect is a SUBSET and says so loudly, because an agent that writes valid SQL and gets "unsupported" back has a worse experience than one told the grammar up front. Grammar() returns the exact supported surface for the tool description.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrTooManyGroups = errors.New("too many distinct groups")
ErrTooManyGroups fires when a GROUP BY has unbounded cardinality — grouping by distinct_id or by a raw URL on a busy site. Failing with a message that names the cause beats an OOM, and beats silently returning the first N groups as though they were the answer.
Functions ¶
Types ¶
type Col ¶
type Col struct{ Name string }
Col is a bare event field: name, distinct_id, timestamp, id.
type Limits ¶
type Limits struct {
MaxRows int // rows returned
MaxGroups int // distinct GROUP BY keys held at once
Timeout time.Duration // wall clock
}
Limits bound what one query may cost. A query surface with no ceiling is an outage waiting for its first curious agent.
func DefaultLimits ¶
func DefaultLimits() Limits
type Prop ¶
type Prop struct{ Key string }
Prop is a property lookup, written prop.country or properties.country.
type Query ¶
type Ref ¶
type Ref struct{ Name string }
Ref is an identifier that is not a known column — it may still be a SELECT alias, which is only knowable once the whole statement is parsed. Resolved by resolveRefs before execution; anything still unresolved then is a genuine typo and becomes an error there.
type Result ¶
type Result struct {
Columns []string `json:"columns"`
Rows [][]any `json:"rows"`
Scanned int `json:"scanned"`
Groups int `json:"groups,omitempty"`
Truncated bool `json:"truncated,omitempty"`
Elapsed string `json:"elapsed"`
}
Result is one answered query. Columns is ordered; Rows are parallel to it.
type Scanner ¶
Scanner streams events. This is the whole store contract the SQL layer needs, and taking the streaming one rather than a slice is what keeps memory O(groups) instead of O(events).