Scaffold PRO#
The edg scaffold command is an interactive wizard that generates a complete workload config. It walks you through driver selection, table definitions, and column types, then outputs to stdout.
edg scaffold > workload.edgWalkthrough#
The wizard prompts for three things:
- Database driver - select from pgx, mysql, mssql, oracle, mongodb, cassandra, sqlite, redis, spanner, or dsql.
- Table names - comma-separated list of tables to generate (e.g.
users, orders, products). - Seed row counts - comma-separated count per table (e.g.
10000, 5000, 1000). Tables without a count default to 10000. - Columns per table - for each table, enter
name:typepairs comma-separated (e.g.id:uuid, email:text, age:int). Leave blank for defaults (id:uuid, name:text).
Supported column types#
| Type | SQL Type (pgx) | Expression |
|---|---|---|
uuid | UUID | uuid_v4() |
text | TEXT | gen('name') |
int | INT | uniform.int(1, 10000) |
float | DOUBLE PRECISION | uniform.float(0.0, 100.0, 2) |
bool | BOOLEAN | bool() |
timestamp | TIMESTAMPTZ | timestamp('2024-01-01T00:00:00Z', '2025-01-01T00:00:00Z') |
SQL types are driver-aware. For example, uuid becomes CHAR(36) on MySQL and UNIQUEIDENTIFIER on MSSQL.
Generated Config#
The output includes all standard sections:
| Section | Purpose |
|---|---|
globals | Per-table row count and batch size |
objects | Column expressions for each table (SQL drivers only) |
up | CREATE TABLE statements (or MongoDB create commands, or PING on Redis) |
seed | Batch insert queries using __columns__ and __values__, or positional args on drivers with no objects section |
init | SELECT queries to fetch seeded data for ref_* access |
run | Point read queries using ref |
deseed | TRUNCATE statements (DELETE FROM on SQLite) |
down | DROP TABLE statements |
Example#
edg scaffold > workload.edg
# Select: pgx
# Tables: users, orders
# Row counts: 5000, 10000
# users columns: id:uuid, email:text
# orders columns: id:uuid, total:floatProduces:
let users_rows = 5000
let orders_rows = 10000
let batch_size = 1000
object users {
id = uuid_v4()
email = gen('name')
}
object orders {
id = uuid_v4()
total = uniform.int(0.0, 100.0)
}
up {
create_users `CREATE TABLE IF NOT EXISTS users (
id UUID,
email TEXT
)`
create_orders `CREATE TABLE IF NOT EXISTS orders (
id UUID,
total DOUBLE PRECISION
)`
}
seed {
seed_users(type: exec_batch, count: users_rows, size: batch_size, object: users)
`INSERT INTO users __columns__ __values__`
seed_orders(type: exec_batch, count: orders_rows, size: batch_size, object: orders)
`INSERT INTO orders __columns__ __values__`
}
init {
fetch_users `SELECT * FROM users LIMIT 1000`
fetch_orders `SELECT * FROM orders LIMIT 1000`
}
run {
read_users
`SELECT * FROM users WHERE id = $1` (ref('fetch_users').id)
read_orders
`SELECT * FROM orders WHERE id = $1` (ref('fetch_orders').id)
}
deseed {
clean_users `TRUNCATE TABLE users`
clean_orders `TRUNCATE TABLE orders`
}
down {
drop_users `DROP TABLE IF EXISTS users`
drop_orders `DROP TABLE IF EXISTS orders`
}MongoDB#
When the mongodb driver is selected, the scaffold generates JSON commands instead of SQL and omits the objects section (MongoDB doesn’t use __columns__/__values__). With no object to draw from, the seed query carries its own positional args:
up {
create_orders `{"create": "orders"}`
}
seed {
seed_orders(type: exec_batch, count: orders_rows, size: batch_size)
`{"insert": "orders", "documents": [{"id": $1, "total": $2}]}` (
uuid_v4(),
uniform.float(0.0, 100.0, 2)
)
}SQLite#
The sqlite driver scaffolds ordinary SQL with SQLite’s type affinities (TEXT for uuid, REAL for float, INTEGER for int), and uses DELETE FROM in deseed because SQLite has no TRUNCATE:
up {
create_orders(type: exec) `CREATE TABLE IF NOT EXISTS orders (
id TEXT,
total REAL
)`
}
deseed {
clean_orders(type: exec) `DELETE FROM orders`
}Redis#
The redis driver scaffolds commands rather than SQL. Each row becomes a hash keyed on the first column, up is reduced to a connectivity check, and deseed/down sweep the keyspace with a SCAN + UNLINK script rather than KEYS, which would block the server:
up {
create_orders(type: exec) `PING`
}
seed {
seed_orders(type: exec_batch, count: orders_rows, size: batch_size) `HSET orders:$1 total $2` (
uuid_v4(),
uniform.float(0.0, 100.0, 2)
)
}
run {
read_orders `HGETALL ${ref('fetch_orders').value}`
}The init query returns whole keys under a value column, which is why the read is ref('fetch_orders').value rather than a column name.
Always validate generated configs before running them:
edg validate config --config workload.edg
Next Steps#
After generating a config:
- Review and customise - add foreign key relationships, transactions, run weights, or more complex expressions.
- Validate - run
edg validate config --config workload.edgto check for errors. - Test - use
edg repl --config workload.edgto try expressions interactively. - Run - execute
edg up && edg seed && edg runagainst your database.