Argument Expression Examples#
These expressions are used in the args: list of a run query. Each entry in args: generates a value that is bound to a query parameter ($1, $2, etc.).
Looking for function signatures and return types? See the Function Reference.
Aggregation#
| Expression | Description |
|---|---|
avg('fetch_products', 'price') | Average price across all products |
count('fetch_products') | Total number of rows in the dataset |
distinct('fetch_products', 'category_id') | Number of distinct category IDs across all products |
max('fetch_products', 'price') | Maximum price in the dataset |
median('fetch_products', 'price') | Median price, equivalent to percentile(..., 50) |
median([3, 1, 2]) | Median of an array literal |
min('fetch_products', 'price') | Minimum price in the dataset |
percentile('fetch_products', 'price', 95) | 95th percentile price, linearly interpolated between the two nearest ranks |
percentile(prices, 95) | 95th percentile of an array; p is always the last argument |
stddev('fetch_products', 'price') | Population standard deviation of price (divides by N) |
sum('fetch_products', 'price') | Sum of the price field across all rows |
sum([1, 2, 3]) | Sum of an array literal; every aggregate accepts this single-array form |
variance('fetch_products', 'price') | Population variance of price (divides by N) |
Batch#
The recommended approach for batch inserts is exec_batch or query_batch with __values__:
seed {
populate_users(count: 1000, size: 100)
`INSERT INTO users (email)
__values__` (
gen('email')
)
}For Oracle, use the parameterized form __values__(table(cols)) to generate INSERT ALL ... SELECT 1 FROM DUAL.
The following expressions are used with the driver-specific batch expansion patterns (unnest, JSON_TABLE, etc.):
| Expression | Description |
|---|---|
batch(customers / batch_size) | Drives batched execution: the parent query runs N times with $1 = 0..N-1 |
gen_batch(customers, batch_size, 'email') | Generates unique emails via gofakeit, split into batches |
string_to_array('$1', __sep__) | Splits a batch-expanded placeholder back into rows using the driver-aware separator |
When using driver-specific batch expansion, always use
__sep__instead of a literal comma delimiter. Generated values (names, addresses, etc.) can contain commas, which would silently split a single value into multiple rows and corrupt your data. The__values__approach avoids this issue entirely.
Binary#
| Expression | Description |
|---|---|
bit(8) | Random fixed-length bit string of 8 bits (e.g. 10110011) |
blob(1024) | Random 1KB blob as raw binary data (works across all databases) |
bytes(16) | Random 16 bytes as a hex-encoded CockroachDB/PostgreSQL BYTES literal |
varbit(16) | Random variable-length bit string of 1-16 bits |
Conditionals#
| Expression | Description |
|---|---|
{'fra': 'eu-central-1', 'sin': 'ap-southeast-1'}[env('FLY_REGION')] ?? fail('bad region') | Map lookup with error on unknown value |
arg('price') * float(arg('qty')) | Same as above using named args |
arg(0) * float(arg(1)) | Compute a total from previously generated price and quantity |
arg(0) + " " + arg(1) | Concatenate previously generated firstname and lastname |
coalesce(ref('optional_data').value, 'default') | First non-nil fallback value |
cond(arg(0), gen('email'), nil) | Email if coin flip is true, NULL if false |
cond(gen('number:1,100') > 95, 'premium', 'standard') | Conditional value based on a random roll |
fail('unexpected value') | Stop worker gracefully with an error message |
fatal('missing required config') | Terminate entire process immediately |
first_name + " " + last_name | Bare sibling reference - named args can be referenced directly without arg() |
nullable(gen('email'), 0.3) | 30% chance of NULL, otherwise a random email |
price * float(qty) | Same as above using bare sibling names (named args only) |
Constants & variables#
| Expression | Description |
|---|---|
const(42) | Always passes the integer 42 |
const(null) | Always passes NULL (alias for const(nil)) |
expr(warehouses * 10) | Evaluates an arithmetic expression using globals |
global('warehouses') | Looks up a global by name (equivalent to using the variable directly) |
int(coalesce(env_nil('CUSTOMERS'), 10000)) | Environment variable with default fallback, converted to int |
warehouses * 10 | Direct global reference in an expression (equivalent to expr(...)) |
Dates & times#
| Expression | Description |
|---|---|
after(ref_same('parent').created_at,'1s', '24h') | Random timestamp between 1 second and 24 hours after the parent’s created_at |
before(ref_same('child').deleted_at,'1s', '24h') | Random timestamp between 1 second and 24 hours before the reference timestamp |
date_offset('-72h') | Timestamp 72 hours in the past (e.g. for TTL or expiry columns) |
date('2006-01-02', '2020-01-01T00:00:00Z','2025-01-01T00:00:00Z') | Random date formatted as YYYY-MM-DD |
duration('1h', '24h') | Random duration between 1 hour and 24 hours |
time('08:00:00', '18:00:00') | Random time of day between 08:00 and 18:00 (HH:MM:SS format) |
timestamp_step() | Next monotonic timestamp (requires timestamp_steps in count:) |
timestamp_steps('2024-01-01T00:00:00Z','2025-01-01T00:00:00Z', 10000) | Count given directly: 10,000 evenly spaced timestamps between min and max; sets up timestamp_step() |
timestamp_steps('2024-01-01T00:00:00Z','2025-01-01T00:00:00Z', '5m') | Count from interval: every 5 minutes between min and max; sets up timestamp_step() |
timestamp('2020-01-01T00:00:00Z','2025-01-01T00:00:00Z') | Random timestamp between two dates (RFC3339 format) |
timez('09:00:00', '17:00:00') | Random time of day with timezone suffix (for TIMETZ columns) |
Geographic#
| Expression | Description |
|---|---|
geo_bearing(51.5074, -0.1278, 48.8566, 2.3522) | Initial compass bearing London -> Paris in degrees (~148.1); 0 is north, 90 east |
geo_distance(51.5074, -0.1278, 48.8566, 2.3522) | Great-circle distance London -> Paris in kilometres (~343.5) |
point_wkt(51.5074, -0.1278, 10.0) | Random geographic point as WKT for native geometry columns |
point(51.5074, -0.1278, 10.0).lat | Random geographic point within 10km of London, latitude |
point(51.5074, -0.1278, 10.0).lon | Random geographic point within 10km of London, longitude |
Hierarchical (ltree)#
| Expression | Description |
|---|---|
ltree('Top', 'Science', 'Astronomy') | PostgreSQL/CockroachDB ltree path: Top.Science.Astronomy |
ltree(arg('name')) | Single-label root path from a previously generated name |
ltree(gen('word'), gen('word'), gen('word')) | Random 3-level path from generated words |
ltree(ref('parent').path, arg('name')) | Append a new label to a parent’s path for hierarchical data |
Invalid ltree characters (hyphens, spaces, etc.) are automatically replaced with underscores. Nil and empty parts are skipped.
Identifiers#
| Expression | Description |
|---|---|
seq(1, 1) | Auto-incrementing sequence: 1, 2, 3, … (per worker) |
seq(100, 10) | Auto-incrementing with custom start and step: 100, 110, 120, … |
ulid() | 26-character Crockford base32 ULID; IDs from different milliseconds sort in creation order |
uuid_v1() | Random UUID v1 (timestamp + node ID) |
uuid_v4() | Random UUID v4 (random) |
uuid_v6() | Random UUID v6 (reordered timestamp) |
uuid_v7() | Random UUID v7 (time-ordered, sortable) |
JSON & arrays#
| Expression | Description |
|---|---|
array(2, 5, 'email') | PostgreSQL/CockroachDB array literal with 2-5 random email addresses |
json_arr(1, 5, 'email') | JSON array of 1-5 random email addresses |
json_obj('source', 'web', 'version', 2, 'active', true) | JSON metadata object for a JSONB column |
Network#
| Expression | Description |
|---|---|
inet('192.168.1.0/24') | Random IP address within a CIDR block |
Correlated totals#
| Expression | Description |
|---|---|
distribute_sum(100.00, 3, 7, 2) | 3-7 random amounts that sum exactly to 100.00, each with 2 decimal places |
distribute_sum(arg(1), 3, 7, 2) | Partition a previously computed total across 3-7 child values |
distribute_sum(ref_same('invoices').total, 3, 7, 2) | Partition an invoice’s total into line item amounts |
distribute_weighted(1000, [50, 30, 20], 0, 2) | Exact 50/30/20 split: 500.00,300.00,200.00 |
distribute_weighted(1000, [50, 30, 20], 0.3, 2) | Approximate 50/30/20 split with 30% noise |
distribute_weighted(arg(1), [7, 2, 1], 0.1, 2) | Split a parent value roughly 70/20/10 |
Numeric distributions#
| Expression | Description |
|---|---|
beta.float(2, 5, 0, 100, 2) | Beta-distributed float scaled onto [0, 100] (min/max scale, they don’t clamp) |
exp.float(0.5, 0, 100, 2) | Exponentially-distributed float in [0, 100] with 2 decimal places |
exp(0.5, 0, 100) | Exponentially-distributed integer in [0, 100] |
lognorm.float(1.0, 0.5, 1, 1000, 2) | Log-normally-distributed float in [1, 1000] with 2 decimal places |
lognorm(1.0, 0.5, 1, 1000) | Log-normally-distributed integer in [1, 1000] |
norm.float(50.0, 15.0, 1.0, 100.0, 2) | Normally-distributed float price centred on 50.00, 2 decimal places |
norm.n(50.0, 10.0, 1, 100, 5, 10) | 5-10 unique normally-distributed values as a comma-separated string |
norm(4, 1, 1, 5) | Normally-distributed integer review rating centred on 4, mostly 3-5 |
nurand_n(8191, 1, items, 5, 15) | 5-15 unique NURand values as a comma-separated string |
nurand(1023, 1, customers / districts) | Non-uniform random int using TPC-C NURand |
pareto.float(2.0, 0, 1000, 2) | Continuous Pareto float in [0, 1000] with 2 decimal places |
pareto.int(2.0, 999) | Pareto-distributed integer in [0, 999]; lower values dominate |
uniform.float(0.01, 999.99, 2) | Random float between 0.01 and 999.99 with 2 decimal places |
uniform.int(0, 1) | Uniform random float between 0 and 1 (e.g. for percentages) |
uniform.n(1, 1000, 3, 5) | 3-5 unique uniform values as a comma-separated string |
zipf.int(2.0, 1.0, 999) | Zipfian distribution: hot-key pattern where value 0 is most frequent |
zipf.n(1.1, 1.0, 100000, 5, 15) | 5-15 unique Zipfian item IDs, e.g. TPC-C New-Order lines |
PII & locale#
| Expression | Description |
|---|---|
first_name + " " + last_name | Composed full name from previously generated named args. Bare sibling names are available directly - no arg() wrapper needed. arg('first_name') syntax still works. |
gen_locale('address', 'de_DE') | Full German address with street number, city, and zip |
gen_locale('city', 'fr_FR') | French city name (e.g. Paris, Lyon) |
gen_locale('first_name', 'ja_JP') | Japanese first name (e.g. 太郎, 花子) |
gen_locale('last_name', 'de_DE') | German last name (e.g. Müller, Schmidt) |
gen_locale('name', 'ja_JP') | Independent full name in locale order (東 = 佐藤太郎, 西 = Hans Müller). First and last are picked independently of first_name/last_name args. |
gen_locale('phone', 'ko_KR') | Korean phone number (e.g. 010-1234-5678) |
gen_locale('street', 'es_ES') | Spanish street name (e.g. Gran Vía) |
gen_locale('zip', 'ja_JP') | Japanese postal code (e.g. 123-4567) |
mask('john@example.com', 'email', 4) | Shorter local mask (e.g. ****@example.com) |
mask('john@example.com', 'email') | Masks local part, preserves domain (e.g. ****************@example.com) |
mask('john@example.com') | Deterministic 16-char hex token (e.g. a3f8c1d9e2b74f06). Same input -> same output within a session |
mask('secret', 'asterisk', 4) | 4 asterisks (e.g. ****) |
mask('secret', 'asterisk') | 16 asterisks (e.g. ****************) |
mask('secret', 'base32') | Base32-encoded token, 16 chars (e.g. UP4MDWPCR3YGQKH5) |
mask('secret', 'base64', 8) | Base64-encoded token, 8 chars (e.g. o/jB2eK3) |
mask('secret', 'base64') | Base64-encoded token, 16 chars (e.g. o/jB2eK3TwYKd1==) |
mask('secret', 'redact') | Fixed string [REDACTED], length ignored |
mask(arg('email'), 8) | 8-char hex token of a previously generated email (e.g. a3f8c1d9) |
Supported locales: en_US, ja_JP, de_DE, fr_FR, es_ES, pt_BR, zh_CN, ko_KR. Aliases like ja, de, fr also work.
Generation#
| Expression | Description |
|---|---|
bool() | Random true or false |
gen('number:1,10') | Random integer between 1 and 10 using gofakeit |
hash('user@example.com', 'sha256') | Unkeyed 64-char lowercase hex digest; same input always gives the same output |
hash(arg('email'), 'crc32') | 8-char hex digest for cheap dedup keys or shard selection |
regex('[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}') | Random IPv4 address |
regex('[0-9a-f]{2}(:[0-9a-f]{2}){5}') | Random MAC address |
regex('[A-Z]{2}[0-9]{2} [A-Z]{3}') | Random license plate (e.g. “AB12 CDE”) |
regex('[A-Z]{3}-[0-9]{4}') | Product code matching a regex pattern |
regex('\\([0-9]{3}\\) [0-9]{3}-[0-9]{4}') | Random US phone number |
regex('#[0-9a-f]{6}') | Random hex colour code |
Uniqueness#
| Expression | Description |
|---|---|
uniq_across('emails', "gen('email')") | Cross-query uniqueness pool - values are unique across all queries sharing the same pool name |
uniq_across('ids', "gen('uuid')", 500) | Cross-query pool with custom max retries (default: 100) |
uniq_across('names', "gen('first')", "gen('last')") | Cross-query composite uniqueness |
uniq("gen('email')") | Retry until a unique email is generated (per-query, reset between queries) |
uniq("gen('first')", "gen('last')") | Unique composite tuple across two expressions |
uniq() state resets between queries, so two seed queries using uniq("gen('email')") can produce duplicates. Use uniq_across() when values must be unique across multiple queries (e.g. unique email across users and admins tables). The pool persists for the lifetime of the environment.
Reference data#
Random selection#
| Expression | Description |
|---|---|
ref_diff('fetch_warehouses').w_id | Unique row on each call within a query (no repeats) |
ref_perm('fetch_warehouses').w_id | Random row pinned to this worker for its lifetime |
ref_same('fetch_warehouses').w_id | Same random row for all ref_same calls within a single query execution |
ref('fetch_warehouses').w_id | Random row from the dataset (uniform) |
Distribution-based selection#
| Expression | Description |
|---|---|
beta.ref('products', 2, 5).name | Beta-distributed - alpha and beta shape which rows are favored |
binomial.ref('products', 10, 0.3).name | Binomial-distributed - index centers around n*p |
empirical.ref('products', [1, 2, 2, 3, 5]).name | Empirical CDF from observed data - sample values define the distribution shape |
exp.ref('products', 1.5).name | Exponential - lower-indexed rows selected more frequently |
gamma.ref('products', 2, 1).name | Gamma-distributed - mean index is shape/rate |
lognorm.ref('products', 0.0, 0.5).name | Log-normal - right-skewed, early rows favored |
norm.ref('products', 0.5, 0.2).name | Normal - mean and stddev as fractions of dataset length |
pareto.ref('fetch_warehouses', 2.0).w_id | Pareto - first rows are strongly favored |
poisson.ref('products', 3.0).name | Poisson - index centers around lambda |
ref_weighted('data.cities', [556, 278, 139, 27]).name | Weighted; each integer weight controls relative probability |
weibull.ref('products', 1.5, 100).name | Weibull - models failure-rate-based access patterns |
zipf.ref('products', 2.0, 1.0).name | Zipfian - first row is “hottest”, frequency drops off by skew |
Iteration & pagination#
| Expression | Description |
|---|---|
ref_cursor('SELECT id FROM customer ORDER BY id', 1000, 'id', 3) | Cursor with repeat: each row appears 3 times before advancing, constant memory |
ref_cursor('SELECT id FROM customer ORDER BY id', 1000, 'id') | Keyset-paginated cursor; pages through results 1000 at a time, constant memory |
ref_each('SELECT id FROM t', 3) | SQL variant with repeat: each SQL result row appears 3 times in the expansion |
ref_each('SELECT id FROM warehouses ORDER BY id') | Executes a SQL query; each row becomes a separate arg set |
ref_each(customers, 3).id | Exact cardinality: each customer row is returned 3 times before advancing to the next |
ref_each(product_catalog).name | Iterates sequentially through a named reference dataset; same row cached within each iteration |
Multi-value selection#
| Expression | Description |
|---|---|
ref_n('fetch_warehouses', 'id', 3, 8) | Picks 3-8 unique random rows, returns comma-separated field values |
weighted_sample_n('fetch_products', 'id', 'popularity', 3, 8) | Pick 3-8 products weighted by their popularity column |
Populating child tables from parent tables#
When seeding a child table (e.g. account) from every row in a parent table (e.g. customer), use ref_each or ref_cursor instead of manual LIMIT/OFFSET pagination.
ref_each - load all parent rows, iterate in batches#
ref_each('SQL') runs the SQL query, loads all returned rows into memory, then executes the parent query once per row. Combined with __values__, rows are collapsed into multi-row INSERTs controlled by size.
Best for small-to-medium parent tables (under ~1M rows) where loading all IDs into memory is acceptable.
seed {
populate_customer(type: exec_batch, count: customers, size: batch_size, workers: 4)
`INSERT INTO customer (email)
__values__` (gen('email'))
populate_account(size: batch_size, workers: 4)
`INSERT INTO account (balance, customer_id)
__values__` (
initial_balance,
ref_each('SELECT id FROM customer').id
)
}ref_each with exact cardinality#
ref_each(dataset, N) gives each parent row exactly N children. Pass the repeat count as the second argument and set count to count('parent') * N.
ref customers [
{id: 1, name: "Alice"},
{id: 2, name: "Bob"},
{id: 3, name: "Carol"}
]
seed {
populate_orders(type: exec_batch, count: count('customers') * 3, size: 100)
`INSERT INTO orders (id, customer_id, amount)
__values__` (
uuid(),
ref_each(customers, 3).id,
rand_f(10.0, 500.0)
)
}Each customer gets exactly 3 orders (9 total). edg doctor warns if count doesn’t match dataset_size × repeat.
ref_each with N across two tables#
Combine two ref_each datasets to build a cross-product with controlled cardinality. Each warehouse gets exactly 2 inventory rows per product (6 rows per warehouse, 12 total).
ref warehouses [
{id: 1, name: "East"},
{id: 2, name: "West"}
]
ref products [
{id: 10, sku: "BOLT"},
{id: 20, sku: "NUT"},
{id: 30, sku: "WASHER"}
]
seed {
populate_inventory(type: exec_batch, count: count('warehouses') * count('products') * 2, size: 100)
`INSERT INTO inventory (warehouse_id, product_id, quantity)
__values__` (
ref_each(warehouses, count('products') * 2).id,
ref_each(products, 2).id,
rand_i(0, 500)
)
}Output pattern (12 rows):
| warehouse_id | product_id | quantity |
|---|---|---|
| 1 | 10 | … |
| 1 | 10 | … |
| 1 | 20 | … |
| 1 | 20 | … |
| 1 | 30 | … |
| 1 | 30 | … |
| 2 | 10 | … |
| 2 | 10 | … |
| 2 | 20 | … |
| 2 | 20 | … |
| 2 | 30 | … |
| 2 | 30 | … |
ref_cursor - keyset-paginated streaming#
ref_cursor('SQL', page_size, 'cursor_column') pages through the parent table using keyset pagination (WHERE cursor_col > $last_value ORDER BY cursor_col LIMIT page_size). Only one page of rows is in memory at a time.
Best for large parent tables (1M+ rows) where loading all IDs into memory is impractical.
seed {
populate_customer(type: exec_batch, count: customers, size: batch_size, workers: 4)
`INSERT INTO customer (email)
__values__` (gen('email'))
populate_account(size: batch_size, workers: 4)
`INSERT INTO account (balance, customer_id)
__values__` (
initial_balance,
ref_cursor('SELECT id FROM customer ORDER BY id', batch_size, 'id')
)
}ref_cursor with exact cardinality#
ref_cursor('SQL', page_size, 'cursor_col', N) combines keyset pagination with repeat count. Each parent row is returned N times before advancing. Memory stays constant - only one page in memory at a time, same as without repeat.
Best for large parent tables (1M+ rows) where ref_each(query, N) would exhaust memory.
seed {
populate_customer(type: exec_batch, count: 1000000, size: batch_size, workers: 4)
`INSERT INTO customer (email)
__values__` (gen('email'))
populate_account(size: batch_size, workers: 4)
`INSERT INTO account (balance, customer_id)
__values__` (
initial_balance,
ref_cursor('SELECT id FROM customer ORDER BY id', batch_size, 'id', 3)
)
}Each customer gets exactly 3 accounts (3M total). Pages stream 1 batch at a time - no matter how many customers exist.
Set selection#
| Expression | Description |
|---|---|
each(['admin', 'user', 'guest']) | Deterministic cycling; row 0 -> admin, row 1 -> user, row 2 -> guest, then wraps |
exp.set(['low', 'medium', 'high', 'critical'], 0.5) | Exponential distribution; concentrates picks toward first item |
lognorm.set(['free', 'basic', 'pro', 'enterprise'], 0.5, 0.5) | Log-normal distribution (right-skewed toward early indices) |
norm.set([1, 2, 3, 4, 5], 2, 0.8) | Normal distribution; index 2 is most common |
pareto.set(['electronics', 'clothing', 'books', 'food', 'toys'], 2.0) | Pareto distribution; strong power-law skew toward first items |
set(['1', '2', '3', '4', '5'], [5, 10, 20, 35, 30]) | Weighted random; skewed toward 4 and 5 stars |
set(['credit_card', 'debit_card', 'paypal'], []) | Uniform random payment method selection |
zipf.set(['electronics', 'clothing', 'books', 'food', 'toys'], 2.0, 1.0) | Zipfian distribution; strong skew toward first items |
Strings & formatting#
| Expression | Description |
|---|---|
template('ORD-%05d', seq(1, 1)) | Formatted order number: “ORD-00001”, “ORD-00002”, … |
Vectors#
Synthetic (no API required)#
| Expression | Description |
|---|---|
exp.vector(384, 10, 0.1, 0.5) | Exponential centroid selection: cluster 0 is the “hottest”, frequency decays per rate |
lognorm.vector(384, 5, 0.1, 1.0, 0.5) | Log-normal centroid selection: right-skewed toward early clusters |
norm.vector(384, 5, 0.1, 2.0, 0.8) | Normal centroid selection: cluster 2 is most common, bell curve falloff |
pareto.vector(384, 10, 0.1, 2.0) | Pareto centroid selection: cluster 0 is the “hottest”, continuous power-law skew |
vector(32, 3, 0.3) | 32-dimensional vector for testing; higher spread = more cluster overlap |
vector(384, 5, 0.1) | pgvector-compatible 384-dimensional vector with 5 clusters and tight spread |
zipf.vector(384, 10, 0.1, 2.0, 1.0) | Zipfian centroid selection: cluster 0 is the “hottest”, realistic skew |
Real embeddings (requires --embed-api-key)#
| Expression | Description |
|---|---|
embed('fixed search query') | Embed a literal string for similarity search queries |
embed(field('name'), field('description')) | Embed concatenated object fields (joined with space) |
embed(gen('sentence:3')) | Embed a generated sentence via external API |
Non-batched (exec/query)#
Each embed() call makes a separate API request:
insert_product(type: exec)
`INSERT INTO product (name, description, embedding)
VALUES ($1, $2, $3::VECTOR)` (
ref_same('product_catalog').name,
ref_same('product_catalog').description,
embed(ref_same('product_catalog').name, ref_same('product_catalog').description)
)With 100 iterations, this makes 100 API calls (one per row).
Batched (exec_batch/query_batch)#
In batch queries, embed() calls are deferred - placeholders are inserted during arg evaluation, then all pending texts are resolved together at the end of each batch:
populate_product(count: 100, size: 50)
`INSERT INTO product (name, description, embedding)
SELECT n, d, e::VECTOR
FROM unnest(ARRAY[$1], ARRAY[$2], ARRAY[$3]) AS t(n, d, e)` (
ref_each(product_catalog).name,
ref_each(product_catalog).description,
embed(ref_each(product_catalog).name, ref_each(product_catalog).description)
)With count: 100 and size: 50, there are 2 batches of 50. Each batch collects 50 texts, then resolves them in a single API call - 2 API calls instead of 100.
Use --embed-max-batch to cap texts per API call. For example, --embed-max-batch 30 on a 50-row batch produces 2 API calls (30+20) per batch, or 4 total (30+20+30+20).
User-defined expressions#
The expr declaration defines named functions from expr-lang expression strings. Each expression becomes a callable function available in any query arg. Expressions can reference globals, built-in functions, and other expressions. Arguments are available via the args slice.
let total_rows = 10000
let num_buckets = 10
expr rows_per_bucket = total_rows / num_buckets
expr ten_percent = int(ceil(total_rows * 0.1))
expr clamp = max(min(args[0], args[1]), 0)
expr pct_of = int(ceil(args[0] * args[1] / 100))
expr like_prefix = string(args[0]) + '%'
expr pick_label = args[0] > 1000 ? 'large' : 'small'
expr wrapped_offset = abs(args[0] - args[1]) % args[2]
expr power_scale = int(floor(float(args[0]) ** 2))
expr is_active = not (args[0] == 0) and (args[0] != args[1] or args[0] < args[2])
expr safe_val = args[0] ?? args[1]
expr round_ratio = round(float(args[0]) / float(args[1]))
expr normalize = lower(trim(replace(args[0], ' ', '_')))
expr shout = upper(split(args[0], ',')[0])
expr add_piped = (args[0] + args[1]) | int
run {
example_query `SELECT * FROM t WHERE bucket = $1 AND label = $2 AND name LIKE $3 AND score >= $4 AND active = $5 AND tag = $6 AND rank >= $7 AND tier = $8 AND fallback = $9 AND weight = $10 AND grp = $11 AND max_id <= $12 LIMIT $13 OFFSET $14` (
gen('number:1,' + num_buckets),
pick_label(total_rows),
like_prefix('foo'),
pct_of(total_rows, 5),
is_active(1, 2, 100),
normalize(' Foo Bar '),
power_scale(3),
shout('hello,world'),
safe_val('premium', 'basic'),
round_ratio(total_rows, num_buckets),
wrapped_offset(7, 20, num_buckets),
add_piped(rows_per_bucket, ten_percent),
clamp(rows_per_bucket, 500),
ten_percent,
)
}