- database/src/table.{c,h}: per-shard per-class slabs (256 rows,
malloc'd, never moved -- row addresses stable for 9b's row views),
occupancy bitmap, LIFO slot reuse, open-addressing id hash with
tombstones (ids never 0, never reused)
- field encoding walks the same .wob class-table kinds the VM walks:
scalars raw (WO_NIL_SCALAR passes through), Texts copied to db_text,
owned objects flattened recursively to db_rec, containers
element-wise; GCREF refused at encode (the GC bulkhead, defensively)
- two one-way copy gates: insert copies VM values in, read allocates
fresh VM values out -- no VM pointer in a slab, no slab pointer in
the VM, proven by mutating originals after insert
- id discipline: per table per shard, S+1 step N; owner = (id-1) % N;
N-parametric, runs at N=1 until iteration 8, tested at N=3
- choke points: wo_row_insert/wo_row_remove carry the INDEX HOOK
sites Task 4 attaches to; nothing else mutates storage
- runtime/Makefile links database/src into every wovm + test binary
- test_table 827/0 ASan+UBSan; oop-e2e 71/0; log-watcher 7/0;
binding doc docs/plan/oop-vm/04-db-binding.md; CODE-LOGIC.md beside
the code; plan Task 1 checked off
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3.8 KiB
DB binding — row format, id discipline, WAL layout, query subset
Normative companion to the engine plan (
2026-08-01-db-engine-binding.md), the way00-wob-format.mdis normative for the image. Grows with the plan's tasks; this revision covers Task 1 (row storage). Memory-safety doctrine lives in the 9b design's section 6 (the copy bulkhead) — this doc is the format.
Two memory worlds, one crossing rule
Rows store no VM pointer, ever. Values cross from VM heap to row storage
by copy on insert, and back by copy on read (wo_row_read allocates fresh VM
values from the shard's runtime). The engine's own allocations are plain
malloc — never the VM arena, so table growth cannot eat the program's heap
cap, and a heap-exhausted program can still read its data.
Row format
row := header slots
header := id u64 | class_id u32 | flags u32 (16 bytes)
slots := field_cnt × u64, declaration order (the VM object shape)
One 8-byte slot per field, kind-driven — the same kind bytes the .wob
class table carries, walked the same way the VM walks them:
| kind | slot holds | engine-owned shape |
|---|---|---|
SCALAR |
the 8 bytes themselves | — (WO_NIL_SCALAR spells a ?scalar nil) |
TEXT |
pointer, 0 = nil | db_text { len u32; bytes[] } |
OWNED |
pointer, 0 = nil | db_rec { class_id u32; slots[] } — flattened by value, recursively through these same rules |
MULTI |
pointer, 0 = nil | db_multi { elem_kind u8; len u32; items[] }, elements encoded element-wise |
MAP |
pointer, 0 = nil | db_map { key_kind, val_kind u8; len u32; kv pairs } |
GCREF |
never stored | compile error upstream (the GC bulkhead); the engine refuses it defensively as an encode error |
ref T is a SCALAR at this layer — the target row's id. The engine learns
what it references only when the FK checks land (9b plan, Task 3).
Storage
Per shard, per class, created lazily on first insert:
- Slabs of 256 rows (
DB_SLAB_ROWS), malloc'd, never moved or freed while the table lives — a row's address is stable for its lifetime, which is the property 9b's loop-scoped row views stand on. - An occupancy bitmap (one bit per slot, slab-major) and a LIFO free-slot list: removal recycles the slot; a recycled slot is always used before a new slab grows. Ids are never reused; slots are.
- The primary index: an open-addressing hash, id → slot, splitmix64 finalizer, power-of-two capacity, 0.7 load, tombstoned deletes (ids are never 0 and never reused, so the all-ones sentinel cannot collide).
Id discipline
Per table, per shard: shard S of N allocates S+1, S+1+N, S+1+2N, … — the
c-runtime plan's shipped interleave. Creation is coordination-free; a row's
owner shard is (id-1) % N. Milestone 1 runs at N=1 and everything
degenerates to 1, 2, 3, …. Id 0 does not exist (it is the hash's "empty"
and the ?ref's nil).
Choke points
wo_row_insert and wo_row_remove are the only functions that mutate a
table. Task 4's secondary indexes hook exactly these two sites (marked
INDEX HOOK in database/src/table.c); the WAL (Task 2) stages its record
beside the same calls. Anything else touching a slab is a defect by
definition — the doctrine the Rust engine learned and this engine enforces.
Still to come in this document
- Task 2: WAL record framing (
length | crc | payload | commit-mark), payload encoding for typed rows, group-commit ordering, replay rules, torn-tail handling, thewal-checkoracle. - Task 3: the
insertstatement's builtin ids (appended to00-wob-format.md's builtin table) and execution contract. - Task 4: secondary-index format,
@uniquetrap code. - Task 5: the select subset and its builtins.