OriginChain docs

Schema for Row CRUD.

schema · 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"