Plan reference (seedling.yaml)
seedling init writes a fully specified plan. Every key is optional when you write one by hand: the plan is the inferred draft overlaid with whatever you specify.
seed: 42 # integer; --seed overrides
locale: en_US # en_US | en_IN | de_DE; --locale overrides
defaults:
date_from: '2023-01-01'
date_to: '2025-12-31' # default range for date/datetime generators
tables:
<table>:
rows: ... # see below
fixed: false # true: --scale does not apply (reference tables)
locale: en_IN # per-table locale
columns:
<column>: ... # generator spec
computed:
<column>: ... # aggregate spec
rows
| form | meaning |
|---|---|
rows: 5000 |
exactly 5000 rows, times --scale (rounded, at least 1) unless fixed: true |
rows: {per: customers, min: 3, max: 12, dist: poisson, lambda: 5} |
for every row of customers, draw a count and create that many rows whose FK points at it |
Per-parent keys: per (parent table), via (FK column when there are several FKs to that parent), min, max, dist (poisson with lambda, uniform, normal with mean/sigma, fixed with n). Draws outside [min, max] are re-drawn (up to 30 times, then clamped), so the shape stays close to the requested distribution. --scale reaches per-parent tables through their parents.
columns
A column is either a generator name (email: email) or a mapping with gen plus parameters. Without gen the mapping is merged into the inferred spec: status: {null_ratio: 0} keeps the inferred generator.
Common parameters:
| key | meaning |
|---|---|
gen |
generator name, see generators.md |
null_ratio |
probability of NULL (inferred as 0.1 for nullable, non-key columns) |
locale |
override the table/plan locale for this column |
Columns are generated in dependency order (an email built from first_name/last_name runs after them). A circular dependency is reported.
computed
Post-pass aggregates over child rows:
orders:
computed:
total: {agg: sum, of: order_items.line_total, by: order_id, decimals: 2}
item_count: {agg: count, of: order_items.id, by: order_id}
agg is sum | count | avg | min | max; of is child_table.column; by is the child's FK column pointing at this table; decimals rounds the result (default 2). While the parent is written the column holds a placeholder (0 for NOT NULL columns, NULL otherwise); parents without children keep it. The final values are written with UPDATEs (SQLite/PostgreSQL), trailing UPDATE statements (SQL dump) or by rewriting the file (CSV/JSONL).
Constraints and the plan
- UNIQUE / PRIMARY KEY (single or composite): offending columns are regenerated, up to 200 attempts; text columns fall back to numeric suffixes after 4 failed attempts. Exhausting a domain (20 rows from
int 1..5) raisescould not satisfy UNIQUE on t.n. - CHECK: simple bounds and IN-lists are turned into generator ranges/enums by
init; everything else is enforced by rejection sampling over the columns the CHECK mentions. - FK cycles: the first table (alphabetically) of the cycle that has a nullable FK into it gets that FK generated as NULL, then filled once the target table exists.