edg-lang Specification#
Version: 1.0 Status: Draft
edg-lang is a domain-specific language for defining database workloads: schema setup, data generation, query execution, and load testing. It compiles to an internal Request structure that drives the edg runtime.
1. Lexical Structure#
1.1 Source Encoding#
Source files are UTF-8 encoded. The lexer operates on byte sequences but supports Unicode letters in identifiers.
1.2 Line Termination#
Newlines (\n, \r\n, \r) are significant tokens - they terminate expressions and declarations. Newlines inside parentheses, brackets, braces, and backtick-delimited strings are consumed as whitespace.
1.3 Whitespace#
Spaces and tabs between tokens are insignificant and ignored (except inside strings and raw strings).
1.4 Comments#
Line comments begin with # and extend to the end of the line:
# This is a comment
let batch_size = 1000 # inline commentThere are no block comments.
1.5 Identifiers#
identifier = letter { letter | digit | "_" } .
letter = "a"…"z" | "A"…"Z" | "_" | <Unicode letter> .
digit = "0"…"9" .Identifiers name variables, objects, queries, fields, and other declarations. Keywords may be used as identifiers in positions where identifiers are expected (query names, field names, option keys).
1.6 Numeric Literals#
number = [ "-" ] digit { digit } [ "." digit { digit } ] .Numbers are signed integers or floating-point values. A leading - is part of the number token only when followed immediately by a digit.
Examples: 42, -1, 3.14, -0.5
1.7 String Literals#
Strings are delimited by single quotes (') or double quotes ("). Backslash escapes (\\, \', \") are supported. Strings may span multiple lines.
'hello world'
"it's a string"
'escape \'this\''1.8 Raw Strings (SQL)#
Backtick-delimited strings preserve all content verbatim, including whitespace and newlines. They are used for SQL, CQL, and MongoDB query bodies.
`SELECT id, name FROM users WHERE id = $1`Multi-line raw strings are automatically dedented: the minimum leading whitespace of non-first lines is removed.
1.9 Operators#
Single-character: +, -, *, /, %, <, >, !, &, |, ^
Multi-character: <=, >=, !=, ==, &&, ||
Operators appear inside expressions, which are evaluated by the expr-lang engine at runtime.
1.10 Punctuation#
| Token | Meaning |
|---|---|
{ } | Block delimiters (sections, objects, ref rows, weights, etc.) |
( ) | Options, arguments, function calls |
[ ] | Array/list delimiters (ref data, tree levels) |
= | Assignment (let, field, weight) |
, | Separator (arguments, options, ref fields) |
: | Key-value separator (options, ref rows, named args) |
. | Member access (in expressions) |
; | Expression separator |
2. Source File Structure#
An edg source file consists of the following sections, in order:
- Includes / Imports - must appear before all other declarations
- Declarations -
let,object,ref,seq,expr,signal,template - Sections -
up,seed,deseed,down,init,run - Configuration blocks -
weights,expect,stages,workers,tree,complete
Declarations and configuration blocks may be interleaved after includes/imports, but includes and imports must precede everything else.
Each section may appear multiple times; entries are appended.
3. Includes and Imports#
3.1 Includes#
include "path/to/file.edg"pathis relative to the including file.- All declarations from the included file are merged directly (no namespacing or
pubrequired). - Merged sections:
let,object,ref,expr,signal,seq,template,up,seed,deseed,down,init,run,workers,weights,stages,expect,tree. - Circular includes are detected and rejected.
- All includes must appear before any declarations.
include is the edg-lang equivalent of YAML’s !include. Use it for flat file splitting where you want everything merged as-is.
3.2 Imports#
import "path/to/file.edg"
import "shared.edg" as sharedpathis relative to the importing file.- Only
pub-marked declarations from the imported file are visible. - With
as alias, imported names are prefixed:alias.name. - Without
as, the filename stem (without.edg) is used as the namespace prefix. - Imported sections (
up,seed,down,init,deseed,run,workers,weights) are namespaced with the import alias. - Circular imports are detected and rejected.
- All imports must appear before any declarations.
4. Declarations#
4.1 let - Variables#
let name = value
pub let name = valueDefines a global variable. The value is a scalar expression evaluated at parse time. Values may be integers, floats, booleans, or expression strings.
let batch_size = 1000
let warehouses = int(coalesce(env_nil('WAREHOUSES'), 1))Variables are available in all subsequent expressions via their name.
4.2 object - Data Generators#
object name {
field = expression
sub {
field = expression
}
}Defines a named data generator with typed fields. Each field is an expression evaluated per-row.
The sub block contains fields that generate nested/array-valued data. Sub-fields are evaluated before regular fields, matching YAML Object evaluation order.
object customer {
id = uuid_v4()
email = gen('email')
name = gen('name')
sub {
orders = obj_n('order', 1, 5)
}
}4.3 ref - Reference Data#
ref name [
{key: value, key: value}
{key: value, key: value}
]Defines inline static reference data as a list of records. Values may be strings, numbers, booleans (true/false), or arrays ([...]). Identifiers in value position are treated as constants.
ref regions [
{id: 1, name: "US", weight: 60}
{id: 2, name: "EU", weight: 30}
{id: 3, name: "APAC", weight: 10}
]Array values are enclosed in square brackets with comma-separated elements:
ref regions [
{name: "us", cities: ["new york", "chicago", "la"]}
{name: "eu", cities: ["london", "paris", "berlin"]}
]Fields within a row are separated by commas. Rows are separated by newlines.
4.4 seq - Sequences#
seq name(start: N, step: M)
seq name(start: N, step: M, length: L)Defines a named numeric sequence.
| Parameter | Required | Description |
|---|---|---|
start | Yes | Initial value |
step | Yes | Increment per call |
length | No | If set, creates a bounded alphabetic sequence |
Without length: monotonic counter (start, start+step, start+2*step, …).
With length: generates alphabetic sequences of the given length (0-based letter sequences).
seq order_id(start: 1, step: 1)
seq sku_code(start: 0, step: 1, length: 3)4.5 expr - Named Expressions#
expr name = expressionDefines a reusable expression that can be referenced by name in other expressions, templates, or query arguments.
expr is_active = result().status == 'active'
expr order_total = result().quantity * result().price4.6 signal - Pre-computed Signal Buffers PRO#
signal name(length: N) { expression }
signal name(from: 'RFC3339', to: 'RFC3339', interval: 'duration') { expression }Defines a named pre-computed signal buffer for correlated temporal patterns across tables. The expression is evaluated for each index i (0 to length-1) at init time.
Length-based:
signal promo_boost(length: 2016) {
floor(50 * pow(cos(pi * mod(i, 500) / 500), 20))
}Duration-based (length auto-computed from time range):
signal traffic(from: '2024-01-01T00:00:00Z', to: '2024-01-08T00:00:00Z', interval: '5m') {
1000 + 400 * sin(2 * pi * i / 288)
}| Option | Required | Description |
|---|---|---|
length | One of | Number of data points |
from | One of | Start timestamp (RFC3339) |
to | One of | End timestamp (RFC3339) |
interval | One of | Time step between data points |
Three functions consume signals:
signal('name')- value at currentiter(), wrapping aroundsignal_at('name', index)- value at explicit indexsignal_correlated('name', lag, correlation)- lagged value with correlated noise (lag is int or duration string)
4.7 template - Query Templates#
template name(options)
template name(options) `SQL`Defines a reusable query template with default options and optional SQL. Queries referencing a template inherit its settings; query-level settings take precedence.
template batch_insert(count: 10000, size: 1000, type: exec_batch)
seed {
populate_users(template: batch_insert)
`INSERT INTO users (id, email) __values__`
(uuid_v4(), gen('email'))
}5. Sections#
Sections group queries for different lifecycle phases. Each section is a keyword followed by a brace-delimited block of queries.
| Section | Purpose | Execution |
|---|---|---|
up | Schema creation (CREATE TABLE, etc.) | Sequential, once |
seed | Data population | Sequential, once |
deseed | Data cleanup before down | Sequential, once |
down | Schema teardown (DROP TABLE, etc.) | Sequential, once |
init | Pre-run initialization (SELECT for reference data) | Sequential, once |
run | Main workload (queries, transactions) | Concurrent, repeated |
up {
create_users `CREATE TABLE users (id UUID PRIMARY KEY, email TEXT)`
}
seed {
populate_users(count: 10000, size: 100)
`INSERT INTO users (id, email) __values__`
(uuid_v4(), gen('email'))
}
init {
fetch_users `SELECT id FROM users`
}
run {
read_user `SELECT * FROM users WHERE id = $1`
(ref_rand('fetch_users').id)
}The run section is special: it supports transactions, if/match control flow, and noop/rollback statements in addition to regular queries.
6. Queries#
6.1 Syntax#
name [(options)] [`SQL`] [(args)]A query has a name, optional parenthesized options, an optional backtick-delimited SQL body, and optional parenthesized arguments.
6.2 Options#
Options are comma-separated key: value pairs inside parentheses.
| Option | Type | Description |
|---|---|---|
type | query | exec | query_batch | exec_batch | Query execution mode |
count | expr | Number of rows/iterations to generate |
size | expr | Batch size for batch types |
object | ident | Object definition to use for field generation |
template | ident | Template to inherit defaults from |
workers | int | Parallel workers for this query |
wait | duration | Delay before execution |
request_timeout | duration | Per-query timeout |
prepared | bool | Use prepared statements |
ignore | bool | Ignore errors |
batch_format | string | Batch output format |
rollback_if | expr | Condition to trigger rollback |
rate | rate | Execution rate (workers block) |
delay | duration | Delay between executions (workers block) |
print | expr or {expr: E, agg: A} | Print values during execution |
post_print | expr or {expr: E, agg: A} | Print values after execution |
Duration format: Go duration strings - 1s, 500ms, 5m, 1h30m.
Rate format: times/interval - 1/10s, 100/1m.
6.3 SQL Body#
SQL is enclosed in backticks. It can be any SQL, CQL, or MongoDB query text. Multi-line SQL is supported and automatically dedented.
Inline arguments: ${expr} inside SQL is replaced with a positional placeholder ($N) and the expression is added to the argument list.
insert_user `INSERT INTO users (id, email) VALUES (${uuid_v4()}, ${gen('email')})`Batch marker: __values__ in INSERT statements is replaced at runtime with generated VALUES clauses for batch inserts.
populate_users(count: 10000, size: 100)
`INSERT INTO users (id, email) __values__`
(uuid_v4(), gen('email'))6.4 Arguments#
Arguments are either positional or named, enclosed in parentheses after the SQL body.
Positional:
(uuid_v4(), gen('email'), uniform(18, 65))Named:
(id: uuid_v4(), email: gen('email'), age: uniform(18, 65))Each argument is an expression evaluated per-row at runtime.
6.5 Query-Scoped Locals#
let bindings may appear between the SQL body and the arguments block. They are evaluated per-row, in declaration order, before the arguments.
query_name(count: 1000, size: 100)
`INSERT INTO t (a, b, c) __values__`
let x = ref_rand('data.cities')
let y = uniform(local('x').lat_min, local('x').lat_max)
(local('x').name, local('y'), gen('email'))Each let creates a local variable accessible via local('name'). Later let bindings can reference earlier ones. Query-scoped locals follow the same clearOneCache boundary as arguments: ref_rand in a local picks a fresh row per data row, while ref_same returns the same row within a single data row.
7. Transactions#
Transactions group queries inside an explicit BEGIN/COMMIT boundary.
transaction name [(options)] {
[let local_var = expr]*
query*
}Options:
| Option | Type | Description |
|---|---|---|
wait | duration | Delay before execution |
ignore | bool | Ignore errors |
ignore_nested | bool | Ignore errors in nested queries |
Local variables: let inside a transaction defines a transaction-scoped variable accessible via local('name').
transaction transfer(wait: 100ms) {
let amount = uniform(1, 100)
read_balance `SELECT balance FROM accounts WHERE id = $1`
(ref_rand('fetch_accounts').id)
if result().balance > local('amount') {
debit `UPDATE accounts SET balance = balance - $2 WHERE id = $1`
(ref_same('read_balance').id, local('amount'))
}
}8. Control Flow#
Control flow statements may appear inside run sections and transaction blocks.
8.1 if / else#
if expr {
(let | query)*
} else {
(let | query)*
}The expression is evaluated at runtime. If truthy, the then block executes; otherwise the optional else block executes. let statements inside branches set transaction-scoped locals that persist after the conditional.
8.2 match / when / default#
match expr {
when value {
(let | query)*
}
when value {
(let | query)*
}
default {
(let | query)*
}
}Evaluates the expression and executes the matching when branch. If no branch matches, default executes (if present). let statements inside branches set transaction-scoped locals that persist after the conditional.
8.3 noop#
A no-operation statement. Takes up a slot in the query list without executing anything.
8.4 rollback / rollback_if#
rollback- unconditionally rolls back the current transaction.rollback_if expr- rolls back if the expression evaluates to true.
9. Weights#
weights {
query_name = weight
query_name = weight
}Assigns relative weights to queries in the run section for random selection. Weights are positive integers.
weights {
read_user = 70
update_user = 20
delete_user = 10
}10. Expectations#
expect {
name [`SQL`] expr
}Post-run assertions. Each expectation has a name, an optional SQL query for context, and a boolean expression.
If SQL is provided, its result columns are available as variables in the expression.
expect {
error_rate < 1
row_count `SELECT count(*) as cnt FROM users` cnt > 0
}11. Stages#
stages {
name(workers: N, duration: D [, ramp_duration: R] [, qps: Q] [, weights: {name: N, ...}])
}Defines sequential load stages with varying worker counts and durations.
| Option | Type | Description |
|---|---|---|
workers | int | Number of concurrent workers |
duration | duration | How long the stage runs |
ramp_duration | duration | Optional linear ramp-up period (must be less than duration) |
qps | int | Optional queries-per-second limit |
weights | {name: N} | Stage-specific query weight overrides |
When ramp_duration is set on a stage with qps, all workers start immediately and QPS increases linearly from near-zero to the target rate over the ramp period. On a stage without qps, workers are spawned incrementally over the ramp period.
stages {
warmup(workers: 5, duration: 30s)
ramp(workers: 50, duration: 2m, ramp_duration: 30s, qps: 1000)
sustain(workers: 100, duration: 10m)
cooldown(workers: 10, duration: 1m, weights: {read_user: 100})
}12. Workers#
workers {
name [(options)] `SQL` [(args)]
}Background workers run continuously in their own goroutine. Worker queries use the same syntax as regular queries, with additional options:
| Option | Type | Description |
|---|---|---|
rate | rate | Execution rate (e.g. 1/10s) |
delay | duration | Fixed delay between executions |
workers {
heartbeat(rate: 1/10s) `SELECT 1`
cleanup(delay: 5m) `DELETE FROM sessions WHERE expires_at < now()`
}13. Trees#
tree name [(options)] `SQL` [(args)] [{ level_overrides }]Generates hierarchical data with parent-child relationships.
Options:
| Option | Type | Description |
|---|---|---|
levels | [N, N, ...] | Number of children per level |
id_column | ident | Column name for the node ID |
parent_column | ident | Column name for the parent reference |
dag | bool | Allow multiple parents (DAG mode) |
type | query type | Execution mode |
size | expr | Batch size |
batch_format | string | Batch format |
wait | duration | Delay between level inserts |
prepared | bool | Use prepared statements |
template | ident | Template reference |
Level overrides: per-level argument overrides in a brace block.
tree org(levels: [1, 3, 5], id_column: id, parent_column: parent_id)
`INSERT INTO employees (id, name, parent_id) __values__` (
id: seq_global('emp_id'),
name: gen('name'),
parent_id: const(null)
) {
level 0 { name: const('CEO') }
level 1 { name: template('VP of %s', gen('buzzword')) }
}14. Complete (AI Generation)#
complete {
tool name [(system: "prompt")] {
property [(type: "T", required: true)]
property [(type: "T")]
}
}Defines an AI tool-calling schema for structured data generation via LLM.
complete {
tool product(system: "Generate realistic product data") {
name(type: "string", required: true)
price(type: "number", required: true)
category(type: "string")
}
}Used with the complete('tool_name') and complete_array('tool_name', N) functions.
15. Expressions#
Expressions in edg-lang are evaluated by the expr-lang engine. They support:
15.1 Operators#
| Category | Operators |
|---|---|
| Arithmetic | +, -, *, /, % |
| Comparison | <, >, <=, >=, ==, != |
| Logical | &&, ||, ! |
| Member access | .field, [index] |
15.2 Literals#
- Integers:
42,-1 - Floats:
3.14,-0.5 - Strings:
'hello',"world" - Booleans:
true,false - Null:
null,nil - Arrays:
[1, 2, 3]
15.3 Function Calls#
function_name(arg1, arg2, ...)All built-in functions (see Section 16) and user-defined globals are accessible in expressions.
15.4 Member Access#
ref_rand('fetch_users').id
result().balance
point(51.5, -0.1, 10).lat15.5 Expression Contexts#
Expressions appear in several contexts:
letvalues:let x = 42 * 3exprdefinitions:expr total = result().qty * result().price- Query arguments:
(ref_rand('users').id, gen('email')) - Query options:
count: warehouses * districts ifconditions:if result().balance > local('amount')matchvalues:match result().statuswhenvalues:when 'active'rollback_ifconditions:rollback_if result().balance < 0expectassertions:error_rate < 1print/post_printvalues:print: result().count- Object fields:
id = uuid_v4()
16. Built-in Functions#
16.1 Data Generation#
| Function | Signature | Description |
|---|---|---|
gen | gen(pattern) | Generate random value via gofakeit pattern |
gen_batch | gen_batch(total, size, pattern) | Generate N values in batches |
gen_locale | gen_locale(locale, pattern) | Locale-aware PII generation |
uuid_v1 | uuid_v1() | Version 1 UUID |
uuid_v4 | uuid_v4() | Version 4 UUID (random) |
uuid_v6 | uuid_v6() | Version 6 UUID |
uuid_v7 | uuid_v7() | Version 7 UUID |
objectid | objectid() | MongoDB ObjectID |
regex | regex(pattern) | String matching regex |
bool | bool() | Random boolean |
bytes | bytes(n) | Hex-encoded random bytes |
blob | blob(n) | Raw random bytes ([]byte) |
bit | bit(n) | Fixed-length bit string |
varbit | varbit(n) | Variable-length bit string |
inet | inet(cidr) | Random IP in CIDR block |
array | array(min, max, pattern) | PostgreSQL array literal |
mask | mask(key, value) | Deterministic PII pseudonymization |
ltree | ltree(parts...) | PostgreSQL ltree path |
16.2 Numeric Distributions#
| Function | Signature | Description |
|---|---|---|
uniform | uniform(min, max) | Uniform random float |
uniform_f | uniform_f(min, max, precision) | Uniform float with decimal places |
zipf | zipf(s, v, max) | Zipfian distribution |
pareto | pareto(alpha, max) | Pareto distribution |
norm | norm(min, max, mean, stddev) | Normal distribution (int) |
norm_f | norm_f(min, max, mean, stddev, precision) | Normal distribution (float) |
norm_n | norm_n(min, max, mean, stddev, n) | N unique normal values |
exp | exp(rate, min, max) | Exponential distribution |
exp_f | exp_f(rate, min, max, precision) | Exponential (float) |
lognorm | lognorm(mu, sigma, min, max) | Log-normal distribution |
lognorm_f | lognorm_f(mu, sigma, min, max, precision) | Log-normal (float) |
nurand | nurand(A, min, max) | TPC-C Non-Uniform Random |
nurand_n | nurand_n(A, min, max, minN, maxN) | N unique NURand values |
16.3 Temporal#
| Function | Signature | Description |
|---|---|---|
timestamp | timestamp(min, max) | Random RFC3339 timestamp |
date | date(format, min, max) | Random date with custom format |
date_offset | date_offset(duration) | Now ± duration |
duration | duration(min, max) | Random Go duration string |
time | time(min, max) | Random time of day (HH:MM:SS) |
timez | timez(min, max) | Random time with timezone |
timestamp_steps | timestamp_steps(min, max, interval_or_count) | Evenly spaced timestamps |
timestamp_step | timestamp_step() | Next monotonic timestamp |
16.4 Geographic#
| Function | Signature | Description |
|---|---|---|
point | point(lat, lon, radiusKM) | Random point → {lat, lon} |
point_wkt | point_wkt(lat, lon, radiusKM) | Random point → WKT |
polygon | polygon(lat, lon, minKM, maxKM, points) | Polygon vertices |
polygon_wkt | polygon_wkt(lat, lon, minKM, maxKM, points) | Polygon → WKT |
16.5 JSON#
| Function | Signature | Description |
|---|---|---|
json_obj | json_obj(key, val, ...) | JSON object string |
json_arr | json_arr(min, max, pattern) | JSON array of generated values |
16.6 Sequences#
| Function | Signature | Description |
|---|---|---|
seq | seq(start, step) | Per-worker auto-increment |
seq_global | seq_global(name) | Shared auto-increment across workers |
seq_rand | seq_rand(name) | Random value from sequence range |
seq_zipf | seq_zipf(name, s, v) | Zipfian from sequence |
seq_pareto | seq_pareto(name, alpha) | Pareto from sequence |
seq_norm | seq_norm(name, mean, stddev) | Normal from sequence |
seq_exp | seq_exp(name, rate) | Exponential from sequence |
seq_lognorm | seq_lognorm(name, mu, sigma) | Log-normal from sequence |
seq_alpha | seq_alpha(length) | Per-worker alpha sequence |
seq_alpha_global | seq_alpha_global(name) | Shared alpha sequence |
16.7 References#
| Function | Signature | Description |
|---|---|---|
ref_cursor | ref_cursor(query, size, col) | Keyset-paginated cursor over SQL query |
ref_each | ref_each(name) | Cycle through rows sequentially |
ref_rand | ref_rand(name) | Random row (uniform) |
ref_same | ref_same(name) | Same row as previous access |
ref_diff | ref_diff(name) | Different row than previous |
ref_perm | ref_perm(name) | Same random row for worker lifetime |
ref_n | ref_n(name, field, n) | N unique random field values |
ref_zipf | ref_zipf(name, s, v) | Zipfian row selection |
ref_pareto | ref_pareto(name, alpha) | Pareto row selection |
ref_weighted | ref_weighted(name, weights) | Weighted row selection with explicit integer weights |
ref_norm | ref_norm(name, mean, stddev) | Normal row selection |
ref_exp | ref_exp(name, rate) | Exponential row selection |
ref_lognorm | ref_lognorm(name, mu, sigma) | Log-normal row selection |
16.8 Set Selection#
| Function | Signature | Description |
|---|---|---|
set_rand | set_rand(items [, weights]) | Random item (uniform or weighted) |
set_zipf | set_zipf(items, s, v) | Zipfian selection |
set_pareto | set_pareto(items, alpha) | Pareto selection |
set_norm | set_norm(items, mean, stddev) | Normal selection |
set_exp | set_exp(items, rate) | Exponential selection |
set_lognorm | set_lognorm(items, mu, sigma) | Log-normal selection |
16.9 Objects#
| Function | Signature | Description |
|---|---|---|
obj | obj(name) | Evaluate object fields |
obj_n | obj_n(name, min, max) | Generate N object instances |
field | field(name) | Access field from current object |
16.10 Runtime Context#
| Function | Signature | Description |
|---|---|---|
result | result() | Last query result row |
results | results() | All rows from last query result |
arg | arg(index_or_name) | Previously evaluated argument |
local | local(name) | Transaction-scoped variable |
global | global(name) | Global config variable |
iter | iter() | 1-based row counter (batch queries) |
global_iter | global_iter() | Monotonic counter across all workers |
batch | batch(n) | Batch indices [0, n) |
env | env(name) | OS environment variable (error if missing) |
env_nil | env_nil(name) | OS environment variable (nil if missing) |
16.11 Aggregation#
| Function | Signature | Description |
|---|---|---|
sum | sum(dataset, field) | Sum numeric field |
avg | avg(dataset, field) | Average numeric field |
min | min(dataset, field) | Minimum value |
max | max(dataset, field) | Maximum value |
count | count(dataset) | Row count |
distinct | distinct(dataset, field) | Distinct value count |
16.12 Utilities#
| Function | Signature | Description |
|---|---|---|
const | const(value) | Literal constant |
expr | expr(value) | Evaluate arithmetic expression |
coalesce | coalesce(a, b, ...) | First non-nil value |
cond | cond(pred, trueVal, falseVal) | Conditional value |
nullable | nullable(probability, value) | NULL with given probability |
template | template(format, args...) | sprintf-style formatting |
uniq | uniq(expr...) | Retry until unique value |
fail | fail(message) | Graceful worker error |
fatal | fatal(message) | Terminate process |
16.13 Math#
| Function | Signature | Description |
|---|---|---|
abs | abs(x) | Absolute value |
ceil | ceil(x) | Ceiling |
floor | floor(x) | Floor |
round | round(x, n) | Round to n decimals |
sqrt | sqrt(x) | Square root |
pow | pow(x, y) | Power |
mod | mod(x, y) | Modulo |
log | log(x) | Natural log |
log10 | log10(x) | Base-10 log |
sin | sin(x) | Sine |
cos | cos(x) | Cosine |
tan | tan(x) | Tangent |
asin | asin(x) | Arcsine |
acos | acos(x) | Arccosine |
atan | atan(x) | Arctangent |
atan2 | atan2(y, x) | Two-argument arctangent |
pi | pi | Pi constant |
16.14 Vectors and Embeddings#
| Function | Signature | Description |
|---|---|---|
vector | vector(dims, centroids) | Clustered vector (uniform) |
vector_norm | vector_norm(dims, centroids, mean, stddev) | Normal centroid |
vector_pareto | vector_pareto(dims, centroids, noise, alpha) | Pareto centroid |
vector_zipf | vector_zipf(dims, centroids, s, v) | Zipfian centroid |
embed | embed(text) | Real vector embedding via API |
16.15 AI Generation#
| Function | Signature | Description |
|---|---|---|
complete | complete(tool_name) | Structured data via LLM tool call |
complete_array | complete_array(tool_name, n) | N items in single LLM call |
16.16 Distribution Helpers#
| Function | Signature | Description |
|---|---|---|
distribute_sum | distribute_sum(total, n) | Partition total into N random parts |
distribute_weighted | distribute_weighted(total, weights) | Proportional partition with noise |
weighted_sample_n | weighted_sample_n(dataset, field, weight_field, n) | N weighted random values |
17. Query Type Inference#
When type is not explicitly specified, edg infers the query type from the SQL verb:
| SQL Prefix | Inferred Type |
|---|---|
SELECT | query |
INSERT, UPDATE, DELETE, UPSERT | exec |
INSERT/UPDATE/DELETE ... RETURNING | query |
INSERT/UPDATE/DELETE ... OUTPUT INSERTED/DELETED | query |
CREATE, DROP, ALTER, TRUNCATE | exec |
MongoDB verbs:
| Verb | Inferred Type |
|---|---|
find, aggregate, count, distinct | query |
insert, update, delete, create, drop, createindexes | exec |
18. Visibility and pub#
The pub keyword exports a declaration for use by importing files.
Supported with: let, object, ref, expr, seq, template.
pub let batch_size = 1000
pub object user { ... }
pub ref regions [ ... ]
pub expr active = result().status == 'active'
pub seq counter(start: 1, step: 1)
pub template batch(count: 1000, type: exec_batch)Declarations without pub are file-private and invisible to importers.
Appendix A: EBNF Grammar#
(* === Top-level === *)
SourceFile = { Include | Import } { Declaration } .
Include = "include" STRING NEWLINE .
Import = "import" STRING [ "as" IDENT ] NEWLINE .
Declaration = LetDecl
| ObjectDecl
| RefDecl
| SeqDecl
| ExprDecl
| TemplateDecl
| Section
| RunSection
| WeightsBlock
| ExpectBlock
| StagesBlock
| WorkersBlock
| TreeDecl
| CompleteBlock .
(* === Declarations === *)
LetDecl = [ "pub" ] "let" IDENT "=" Expr NEWLINE .
ExprDecl = [ "pub" ] "expr" IDENT "=" Expr NEWLINE .
SeqDecl = [ "pub" ] "seq" IDENT "(" SeqOptions ")" .
SeqOptions = SeqOption { "," SeqOption } .
SeqOption = ( "start" | "step" | "length" ) ":" NUMBER .
ObjectDecl = [ "pub" ] "object" IDENT "{" { ObjectField | SubBlock } "}" .
ObjectField = IDENT "=" Expr NEWLINE .
SubBlock = "sub" "{" { ObjectField } "}" .
RefDecl = [ "pub" ] "ref" IDENT "[" { RefRow } "]" .
RefRow = "{" RefField { "," RefField } "}" .
RefField = IDENT ":" Value .
Value = STRING | NUMBER | "true" | "false" | IDENT .
TemplateDecl = [ "pub" ] "template" IDENT [ "(" QueryOptions ")" ]
[ RAWSTRING ] .
(* === Sections === *)
Section = SectionKW "{" { Query } "}" .
SectionKW = "up" | "seed" | "deseed" | "down" | "init" .
RunSection = "run" "{" { RunItem } "}" .
RunItem = Transaction | SpecialQuery | Query .
(* === Queries === *)
Query = IDENT [ "(" QueryOptions ")" ]
[ RAWSTRING ]
[ "(" Args ")" ] .
QueryOptions = QueryOption { "," QueryOption } .
QueryOption = IDENT ":" OptionValue .
OptionValue = Expr
| "{" BraceMap "}" (* print compound form *)
| "{" InlineWeights "}" . (* stage weights *)
Args = PositionalArgs | NamedArgs .
PositionalArgs = Expr { "," Expr } .
NamedArgs = NamedArg { "," NamedArg } .
NamedArg = IDENT ":" Expr .
SpecialQuery = "noop"
| "rollback"
| "rollback_if" Expr NEWLINE
| IfBlock
| MatchBlock .
(* === Transactions === *)
Transaction = "transaction" IDENT [ "(" TxOptions ")" ]
"{" { TxLocal | SpecialQuery | Query } "}" .
TxOptions = TxOption { "," TxOption } .
TxOption = ( "wait" | "ignore" | "ignore_nested" ) ":" Expr .
TxLocal = "let" IDENT "=" Expr NEWLINE .
(* === Control Flow === *)
IfBlock = "if" Expr "{" { SpecialQuery | Query } "}"
[ "else" "{" { SpecialQuery | Query } "}" ] .
MatchBlock = "match" Expr "{"
{ WhenClause }
[ DefaultClause ]
"}" .
WhenClause = "when" Expr "{" { SpecialQuery | Query } "}" .
DefaultClause = "default" "{" { SpecialQuery | Query } "}" .
(* === Configuration Blocks === *)
WeightsBlock = "weights" "{" { IDENT "=" NUMBER NEWLINE } "}" .
ExpectBlock = "expect" "{" { Expectation } "}" .
Expectation = IDENT [ RAWSTRING ] Expr NEWLINE .
StagesBlock = "stages" "{" { StageEntry } "}" .
StageEntry = IDENT "(" StageOptions ")" .
StageOptions = StageOption { "," StageOption } .
StageOption = "workers" ":" NUMBER
| "duration" ":" DURATION
| "ramp_duration" ":" DURATION
| "qps" ":" NUMBER
| "weights" ":" "{" InlineWeights "}" .
InlineWeights = IDENT ":" NUMBER { "," IDENT ":" NUMBER } .
WorkersBlock = "workers" "{" { Query } "}" .
TreeDecl = "tree" IDENT [ "(" TreeOptions ")" ]
[ RAWSTRING ]
[ "(" Args ")" ]
[ "{" { LevelOverride } "}" ] .
TreeOptions = TreeOption { "," TreeOption } .
TreeOption = "levels" ":" "[" NUMBER { "," NUMBER } "]"
| "id_column" ":" IDENT
| "parent_column" ":" IDENT
| "dag" ":" ( "true" | "false" )
| QueryOption .
LevelOverride = "level" NUMBER "{" { IDENT ":" Expr } "}" .
CompleteBlock = "complete" "{" { ToolDecl } "}" .
ToolDecl = "tool" IDENT [ "(" ToolOptions ")" ]
"{" { PropertyDecl } "}" .
ToolOptions = "system" ":" STRING .
PropertyDecl = IDENT [ "(" PropOptions ")" ] .
PropOptions = PropOption { "," PropOption } .
PropOption = ( "type" | "required" | IDENT ) ":" Expr .
(* === Expressions === *)
Expr = (* expr-lang expression: arithmetic, comparisons,
logical operators, function calls, member access,
array literals, string literals, numbers, booleans,
nil/null *) .
(* === Lexical Tokens === *)
IDENT = letter { letter | digit | "_" } .
NUMBER = [ "-" ] digit { digit } [ "." digit { digit } ] .
STRING = "'" { char | escape } "'"
| '"' { char | escape } '"' .
RAWSTRING = "`" { any } "`" .
NEWLINE = "\n" | "\r\n" | "\r" .
DURATION = NUMBER ( "ns" | "us" | "µs" | "ms" | "s" | "m" | "h" ) .Appendix B: Keywords#
The following identifiers are reserved keywords:
| Keyword | Category |
|---|---|
let | Declaration |
object | Declaration |
ref | Declaration |
seq | Declaration |
expr | Declaration |
template | Declaration |
pub | Modifier |
import | Module |
include | Module |
sub | Object block |
transaction | Run block |
if | Control flow |
else | Control flow |
match | Control flow |
when | Control flow |
default | Control flow |
up | Section |
seed | Section |
deseed | Section |
down | Section |
init | Section |
run | Section |
weights | Configuration |
expect | Configuration |
workers | Configuration |
stages | Configuration |
tree | Configuration |
complete | Configuration |
Keywords may be used as identifiers in positions where identifiers are expected (e.g. query names, field names, option keys), per the parser’s expectIdent function which accepts both TokenIdent and keyword tokens.