fix(db2-delta): refuse resident:keys with no WO_DATA at runtime

- loader stopped refusing durable:true+resident:keys once UPDATE
  landed; nothing replaced it at runtime
- rows for such a table live only in the WAL, so every read failed
  with a misleading "no such row" instead of naming the problem
- main.c now refuses at startup, names the class, exit(2)
- residency-accept.sh gains a leg: refuses without WO_DATA, still
  runs with it — verified failing before the fix, passing after

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 3ea6d6452f260d45f92045c0f300fa49faa1d810)
This commit is contained in:
shoney.arickathil 2026-08-30 11:57:57 +02:00
parent e08c26309a
commit 49a0a9d047
2 changed files with 49 additions and 0 deletions

View file

@ -219,6 +219,31 @@ int main(int argc, char **argv) {
VM.rt.db = &DB; VM.rt.db = &DB;
DB.rt = &VM.rt; /* databasev2 2 (5c): the loop a borrow reads the WAL through */ DB.rt = &VM.rt; /* databasev2 2 (5c): the loop a borrow reads the WAL through */
const char *data_dir = getenv("WO_DATA"); const char *data_dir = getenv("WO_DATA");
if (!data_dir || !data_dir[0]) {
/* databasev2 3: the loader used to refuse `resident: keys` outright;
* now it is accepted because UPDATE landed, but every row still
* lives in the log, not RAM — refuse the same way the loader's own
* durable:false+resident:keys refusal does, rather than let reads
* silently misbehave with no WAL to fold from. */
for (uint32_t i = 0; i < mod.class_cnt; i++) {
if (!(mod.classes[i].flags & WO_CLASSF_RESIDENT_KEYS)) continue;
const char *cname = "?";
int cnlen = 1;
uint32_t k = mod.classes[i].name;
if (k < mod.const_cnt && mod.consts[k].s) {
cname = mod.consts[k].s->data;
cnlen = (int)mod.consts[k].s->len;
}
fprintf(stderr,
"wovm: `%.*s` is declared `resident: keys` — its rows live only "
"in the write-ahead log, so it cannot run without WO_DATA.\n",
cnlen, cname);
wo_db_destroy(&DB);
wo_vm_destroy(&VM);
wo_module_free(&mod);
return 2;
}
}
if (data_dir && data_dir[0]) { if (data_dir && data_dir[0]) {
char wal_path[512]; char wal_path[512];
snprintf(wal_path, sizeof wal_path, "%s/shard-0.wal", data_dir); snprintf(wal_path, sizeof wal_path, "%s/shard-0.wal", data_dir);

View file

@ -170,6 +170,30 @@ else
bad "the doc example compiles" "see $LOG" bad "the doc example compiles" "see $LOG"
fi fi
# ---- 6. resident:keys refuses to run without WO_DATA -----------------------
# CRITICAL 1: the loader stopped refusing durable:true + resident:keys once
# UPDATE landed, and nothing replaced that refusal at runtime — a table
# declared this way ran with no WAL to fold its rows from, misreporting
# every read as "no such row" instead of naming the real problem.
printf '@table(name: "items", index: [k], durable: true, resident: keys)\nclass Items { k: Text }\nfn main() -> Int { insert Items { k: "a" }; return 0; }\n' > "$WORK/reskeys.wo"
if "$WOC" --emit "$WORK/reskeys.wo" -o "$WORK/reskeys.wob" 2>"$WORK/e"; then
out="$("$WOVM" "$WORK/reskeys.wob" 2>&1)"; rc=$?
[[ $rc -eq 2 ]] \
&& ok "resident:keys without WO_DATA exits 2" \
|| bad "resident:keys without WO_DATA exits 2" "exit=$rc"
grep -q 'Items' <<<"$out" \
&& ok "resident:keys refusal names the offending class" \
|| bad "resident:keys refusal names the class" "got: $out"
mkdir -p "$WORK/d6"
WO_DATA="$WORK/d6" "$WOVM" "$WORK/reskeys.wob" >/dev/null 2>&1
rc2=$?
[[ $rc2 -eq 0 ]] \
&& ok "resident:keys WITH WO_DATA still runs" \
|| bad "resident:keys with WO_DATA runs" "exit=$rc2"
else
bad "resident:keys refusal fixture compiles" "$(head -1 "$WORK/e")"
fi
echo echo
echo "residency-accept: $((pass + fail)) checks, $fail failures" echo "residency-accept: $((pass + fail)) checks, $fail failures"
[[ $fail -eq 0 ]] || exit 1 [[ $fail -eq 0 ]] || exit 1