writeonce/docs/examples/residency/main.wo
shoney.arickathil e08c26309a feat(db2-delta): lift the resident:keys refusal, prove it end to end
- loader.c: delete the INCOMPLETE-update BAIL; durable:false +
  resident:keys stays refused (nowhere to read from)
- table.c: root-cause fix for the Text-index gap — a keys-resident
  borrow now holds ENGINE values, matching wo_row_ptr's contract
  (table.h's "no VM pointer" doctrine), not a VM-decoded row. Fixes
  idx_hash/idx_cols_equal/wo_idx_probe AND db.c's GET_FIELD/PROBE
  arms with one change; reproduced pre-fix as an ASan
  heap-buffer-overflow
- docs/examples/residency: Product is genuinely resident:keys;
  residency-accept.sh's refusal leg replaced by proving the program
  runs and stock survives a restart (11/0)
- test_wal.c: oracle test drives resident:all and resident:keys
  through the same update sequence and asserts identical rows;
  Text-indexed-update test catches the representation bug; five
  pre-existing tests corrected to the fixed contract (4746/0)
- story, README, status board, CODE-LOGIC.md updated; three known
  limitations documented: mid-drain stale reads, O(N^2) replay in
  chain length, compaction blind to per-row chain length

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit b87c68f950f01aa5e572fbb86a0f374adc83d813)
2026-08-30 20:37:49 +02:00

117 lines
4.6 KiB
Text

-- residency — databasev2 2's per-table storage, demonstrated across a restart
-- with the workload the feature exists for: a product catalogue whose stock
-- moves every time an order is placed.
--
-- Before this iteration, durability was ONE environment variable for a whole
-- process: WO_DATA set and every @table is WAL-logged, or unset and none are.
-- Real applications are not uniform. A cart session is disposable; a product's
-- stock level is not; and a catalogue large enough to matter does not fit in
-- RAM at all. One global switch forces "everything is precious" or "nothing
-- is", and the developer pays for whichever is wrong.
--
-- Run it twice against the same WO_DATA directory:
--
-- mkdir -p /tmp/residency-data -- WO_DATA must exist already
-- WO_DATA=/tmp/residency-data wovm residency.wob seed
-- WO_DATA=/tmp/residency-data wovm residency.wob order
--
-- The second run places an order. Stock comes back decremented after a
-- restart; the cart does not come back at all.
-- Precious AND read-selectively resident: WAL-logged, replayed at boot, but
-- only the id -> log-offset map lives in RAM — each row's payload is read
-- back from the log. A real catalogue is the table that outgrows RAM first,
-- so this is the mode you would actually reach for one. Storage, reads,
-- scans through the `sku` index below (a Text column — exercised here, not
-- just the scalar columns other tests stuck to), `@unique`, deletes and
-- checkpoint survival all work, and so does UPDATE: `place_order` below moves
-- `stock` through a WAL delta record — read-modify-APPEND, not a slab
-- mutation, since a keys-resident row has no slab slot to mutate. See the
-- README for what a delta update costs a reader.
--
-- Only `stock` changes when an order is placed; `sku`, `name` and `price` do
-- not. Appending the whole row on every sale would rewrite every field to
-- change one integer, on the hottest write path a shop has — which is why
-- the delta is one field, not a full-row rewrite.
@table(name: "products", index: [sku], durable: true, resident: keys)
class Product {
sku: Text
name: Text
price: Int
stock: Int
}
-- Scratch: never written to the WAL, so it costs no disk and no fsync, and it
-- is EMPTY after a restart. That is the point — not a bug to work around.
@table(name: "carts", index: [token], durable: false)
class Cart {
token: Text
sku: Text
}
fn count_products() -> Int {
let n = 0;
for _p in from p in Product select p { n = n + 1; }
return n;
}
fn count_carts() -> Int {
let n = 0;
for _c in from c in Cart select c { n = n + 1; }
return n;
}
fn stock_of(sku: Text) -> Int {
for p in from p in Product where p.sku == sku select p { return p.stock; }
return 0 - 1;
}
-- The update this example exists to show: one field of one row moves, and the
-- other three do not.
fn place_order(sku: Text, qty: Int) -> Int {
for p in from p in Product where p.sku == sku select p {
if p.stock < qty { return 0 - 1; }
p.stock = p.stock - qty; -- writes through to the engine
return p.stock;
}
return 0 - 1;
}
fn main(args: multi Text) -> Int {
if len(args) > 0 {
if args[0] == "seed" {
insert Product { sku: "SKU-1", name: "kettle", price: 2999, stock: 10 };
insert Product { sku: "SKU-2", name: "mug", price: 799, stock: 40 };
-- a cart is scratch: same insert, different annotation, different fate
insert Cart { token: "cart-a", sku: "SKU-1" };
print("seeded: products=${count_products()} carts=${count_carts()} SKU-1 stock=${stock_of("SKU-1")}");
return 0;
}
if args[0] == "order" {
-- second run: no inserts. Whatever is here came from the log.
let before = stock_of("SKU-1");
let after = place_order("SKU-1", 3);
print("after restart: products=${count_products()} carts=${count_carts()}");
print("order: SKU-1 stock ${before} -> ${after}");
if count_carts() != 0 {
print("UNEXPECTED: a durable:false table survived a restart");
return 1;
}
if after != before - 3 {
print("UNEXPECTED: the stock update did not apply");
return 1;
}
-- Run `order` more than once and this is the interesting line: a stock
-- level below the seeded 10 can only mean an EARLIER order's update
-- survived a restart. The decrement is durable, not just the insert.
if before < 10 {
print("ok: an earlier order's decrement replayed from the log");
} else {
print("ok: first order placed; run `order` again to see it replay");
}
return 0;
}
}
print("usage: residency seed | residency order");
return 0;
}