SignalQL v0.1 specification
Readable product-facing specification for authors and implementers. Grammar is normative for parsers; semantics describe execution and outputs.
Core concepts
- Events: timestamped occurrences with a name and optional JSON properties.
- Users: actors identified by
user_idwith optional trait JSON. - Sessions: optional grouping of events in time for funnel ordering.
- Properties: JSON payloads on events (
properties) or users (traits). - Time ranges: windows restricting analysis, e.g. last N complete days.
Grammar (EBNF)
ebnf
query ::= aggregate_query | funnel_query
aggregate_query ::= aggregate_clause source_clause filter_clause time_clause group_clause?
funnel_query ::= "FUNNEL" funnel_step ( "THEN" funnel_step )+ source_clause time_clause
aggregate_clause ::= "COUNT" distinct_opt aggregate_target
distinct_opt ::= "DISTINCT" | ε
aggregate_target ::= "EVENTS" | "USERS"
source_clause ::= "FROM" identifier
filter_clause ::= "WHERE" predicate_list
predicate_list ::= predicate ( "AND" predicate )*
predicate ::= field_path "=" string_literal
field_path ::= identifier ( "." identifier )*
time_clause ::= "DURING" time_window
time_window ::= "LAST" integer "DAYS"
| "BETWEEN" timestamp_literal "AND" timestamp_literal
group_clause ::= "GROUP" "BY" group_dim
group_dim ::= "DAY"
funnel_step ::= string_literal
string_literal ::= '"' { character } '"'
identifier ::= [a-zA-Z_] [a-zA-Z0-9_]*
integer ::= [0-9]+Whitespace is insignificant outside string literals. Keywords are case-insensitive in this reference implementation.
Reference parser notes for v0.1:
- Aggregate queries require both
WHEREandDURING. - Funnel queries require at least two steps plus
FROMandDURING. BETWEENis grammar-valid; funnel compilation withBETWEENis deferred in the Postgres reference compiler.
Execution model
- Parse the query into an AST; reject unknown constructs.
- Bind identifiers to the portable model (
eventsdefault source). - Compile using dialect-specific adapter behavior.
- The Postgres reference compiler emits parameterized SQL and never interpolates user string literals into SQL text.
- Other adapters must explicitly document whether they use placeholders + params, escaped literal SQL, or host-side binding requirements.
- Enforce limits: max date span (default 366 days), max funnel steps (default 6), max result rows for grouped queries (adapter-defined).
Result shapes
| Query shape | Result columns (logical) |
|---|---|
COUNT EVENTS | single row: count |
COUNT USERS DISTINCT | single row: count |
... GROUP BY DAY | day, count |
FUNNEL ... | step, users or conversion_rate |
Exact column names may vary by dialect; adapters document mappings.
Examples
Event counts
signalql
COUNT EVENTS FROM events WHERE event_name = "signup" DURING LAST 30 DAYSDaily series
signalql
COUNT EVENTS FROM events WHERE event_name = "view_item" DURING LAST 14 DAYS GROUP BY DAYTwo-step funnel
signalql
FUNNEL "signup" THEN "activated" FROM events DURING LAST 7 DAYSSegmentation
signalql
COUNT USERS FROM events WHERE properties.plan = "pro" AND event_name = "purchase" DURING LAST 28 DAYSDeterminism
For identical inputs and compiler version, generated SQL text and parameter ordering are stable. Adapters must not introduce nondeterministic ordering without documenting it.
Normative references
- Portable data model: Data model
- Reference compiler package:
@signalql/compiler