sql

package
v0.31.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 9 Imported by: 0

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

View Source
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

func Grammar

func Grammar() string

Grammar is the supported surface, verbatim, for the MCP tool description. An agent that is told the grammar writes queries that run; one that has to guess writes SQL that parses everywhere else and fails here.

Types

type Binary

type Binary struct {
	Op          string
	Left, Right Expr
}

Binary is a comparison, arithmetic or boolean operator.

func (Binary) String

func (b Binary) String() string

type Call

type Call struct {
	Name     string
	Args     []Expr
	Distinct bool // count(distinct x)
}

Call is a function or aggregate: count, sum, avg, min, max, date, hour, lower, ...

func (Call) String

func (c Call) String() string

type Col

type Col struct{ Name string }

Col is a bare event field: name, distinct_id, timestamp, id.

func (Col) String

func (c Col) String() string

type Expr

type Expr interface{ String() string }

type InList

type InList struct {
	X      Expr
	Vals   []Expr
	Negate bool
}

InList is `x IN (a, b, c)`.

func (InList) String

func (i InList) String() string

type IsNull

type IsNull struct {
	X      Expr
	Negate bool
}

IsNull is `x IS NULL` / `x IS NOT NULL`.

func (IsNull) String

func (i IsNull) String() string

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 Lit

type Lit struct{ Val any }

Lit is a literal string, number, boolean or NULL.

func (Lit) String

func (l Lit) String() string

type OrderItem

type OrderItem struct {
	Expr Expr
	Desc bool
}

type Prop

type Prop struct{ Key string }

Prop is a property lookup, written prop.country or properties.country.

func (Prop) String

func (p Prop) String() string

type Query

type Query struct {
	Select  []SelectItem
	Where   Expr
	GroupBy []Expr
	Having  Expr
	OrderBy []OrderItem
	Limit   int
}

func Parse

func Parse(src string) (*Query, error)

Parse turns one SELECT statement into a Query. Anything that is not a SELECT over `events` is rejected here, which is what makes the whole surface read-only.

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.

func (Ref) String

func (r Ref) String() string

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.

func Run

func Run(q *Query, sc Scanner, lim Limits) (*Result, error)

Run executes a parsed query against a scanner.

type Scanner

type Scanner interface {
	Scan(from, to time.Time, fn func(event.Event) error) error
}

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).

type SelectItem

type SelectItem struct {
	Expr  Expr
	Alias string
}

type Star

type Star struct{}

Star is `*`, only legal inside count(*).

func (Star) String

func (Star) String() string

type Unary

type Unary struct {
	Op string
	X  Expr
}

Unary is NOT, or unary minus.

func (Unary) String

func (u Unary) String() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL