Schema for Row CRUD.
The k/v front door — typed shortcut to read and write rows by primary key. Same constraints (FK, CHECK) are enforced as SQL INSERT goes through. Single-column PK is the gate for the GET /rows/:schema/:pk point-read fast path.
Engine surface: GET /v1/tenants/:t/rows/:schema/:pk, POST /v1/tenants/:t/rows/:schema, POST /v1/tenants/:t/rows/:schema/_batch (body-limit disabled).
Required schema fields.
Without these, this query surface doesn't function at all.
| field | effect |
|---|---|
| namespace + table | Table addressable as <code class='mono text-[12px] text-[var(--color-accent)]'>shop.products</code> in the URL. |
| primary_key = ["id"] (single column) | The /rows/:schema/:pk point-read shortcut requires a single-column PK. Multi-column PK tables read via SQL instead. |
| [[columns]] declarations | Required for typed serialisation. Body fields not in [[columns]] are rejected with 400 unknown field. |
Optional fields — what each one unlocks.
Add only the fields whose effect you need. Each one buys a specific capability — speed up a predicate, guard a write, or unlock a new query shape.
| field | type | default | effect |
|---|---|---|---|
| [[columns]] required = true | bool | false | Write fails with 400 if column missing from body. Use for PK + business-required fields. |
| [[foreign_keys]] | object | — | Enforced on every write through this endpoint — same as SQL INSERT. Refuses orphan refs with 409. |
| [[check_constraints]] | object | — | Enforced on every write. Returns 409 check_violation on fail. |
| _batch body shape: raw array of rows | [object] | — | Body-limit disabled on the _batch route. Supports multi-GB bulk ingest. Wrap as `[{...}, {...}]` not `{rows: [...]}`. |
Endpoints that work once required fields are in.
- GET /rows/:schema/:pk — point read by primary key
- POST /rows/:schema — insert one row (fields flat at top level)
- POST /rows/:schema/_batch — bulk insert (raw array)
- PUT /rows/:schema/:pk — overwrite-on-PK
- put_row_cas (SDK only) — compare-and-set on _oc_row_version witness
- DELETE /rows/:schema/:pk — delete by primary key
Edge cases.
The engine returns a typed 400 with a hint instead of running these. Knowing them up front avoids a debugging round-trip.
| shape | why |
|---|---|
| /rows/:schema/:pk on a composite PK | Point-read shortcut is single-column-PK only. Composite PK tables read via SQL — WHERE pk1 = X AND pk2 = Y. |
| Bulk insert with { rows: [...] } wrapping | _batch expects a RAW array. The wrapped form returns 400 invalid type: map, expected a sequence at line 1. |
Abbreviation legend.
| token | meaning |
|---|---|
| PK | Primary key — column(s) listed in primary_key = [...] |
| CAS | Compare-And-Set — optimistic concurrency control via _oc_row_version witness |
| _oc_row_version | Engine-maintained version counter on every row. Increments on every write |
| _batch | URL suffix for the body-limit-disabled bulk-insert endpoint |
| last-writer-wins | Two concurrent writes to the same PK without CAS: the latest write replaces the earlier one entirely |
Worked example.
Schema TOML — copy + register via POST /v1/tenants/:t/schemas with Content-Type: text/plain.
namespace = "shop"
table = "products"
primary_key = ["id"] # single column — enables /rows/:pk shortcut
[[columns]]
name = "id"
ty = "str"
required = true
[[columns]]
name = "name"
ty = "str"
required = true
[[columns]]
name = "category"
ty = "str"
[[columns]]
name = "price_cents"
ty = "i64"
[[columns]]
name = "description"
ty = "str"
# Optional FK enforced on every write (SQL INSERT + /rows/_batch + tx)
[[foreign_keys]]
name = "fk_category"
from_col = "category"
target_schema = "shop.categories"
target_col = "id"
on_delete = "no_action"
[[check_constraints]]
name = "price_positive"
expression = "price_cents > 0" Queries it enables.
# Point read by PK (sub-millisecond)
curl $BASE/v1/tenants/$T/rows/shop.products/p001 -H "Authorization: Bearer $BEARER"
# Single insert (flat fields, NOT nested under 'row')
curl -X POST $BASE/v1/tenants/$T/rows/shop.products -H "Authorization: Bearer $BEARER" \
-H "Content-Type: application/json" \
-d '{ "id": "p-new-1", "name": "New Product", "category": "electronics", "price_cents": 9999, "description": "demo" }'
# Bulk insert (RAW array, NOT wrapped as { rows: [...] })
curl -X POST $BASE/v1/tenants/$T/rows/shop.products/_batch -H "Authorization: Bearer $BEARER" \
-H "Content-Type: application/json" \
-d '[
{ "id": "p100", "name": "Batch A", "category": "books", "price_cents": 1500, "description": "..." },
{ "id": "p101", "name": "Batch B", "category": "apparel", "price_cents": 3000, "description": "..." },
{ "id": "p102", "name": "Batch C", "category": "grocery", "price_cents": 999, "description": "..." }
]'
# Delete
curl -X DELETE $BASE/v1/tenants/$T/rows/shop.products/p-new-1 -H "Authorization: Bearer $BEARER"