diff --git a/compiler/test/runner.ml b/compiler/test/runner.ml index f120542..1d3846b 100644 --- a/compiler/test/runner.ml +++ b/compiler/test/runner.ml @@ -2604,7 +2604,7 @@ let validate_image (img : string) : string list = golden lowering suite actually emits; 61 = DB_INSERT (arity 1: the class-id slot — field slots are runtime-validated, same as the C loader) *) - if c > 12 && (c < 61 || c > 63) then + if c > 12 && (c < 61 || c > 66) then fail (Printf.sprintf "method %d pc %d: builtin out of range" i pc) else if c = 4 then begin if b > 5 then fail (Printf.sprintf "method %d pc %d: bad element kind" i pc) @@ -2623,6 +2623,9 @@ let validate_image (img : string) : string list = | 61 -> 1 | 62 -> 4 | 63 -> 2 + | 64 -> 1 + | 65 -> 3 + | 66 -> 3 | _ -> 0 in if arity > 0 then begin diff --git a/database/src/db.c b/database/src/db.c index 790a21b..a370550 100644 --- a/database/src/db.c +++ b/database/src/db.c @@ -1,5 +1,8 @@ #include "db.h" +#include + +#include "cont.h" #include "table.h" #include "wal.h" @@ -68,6 +71,85 @@ int wo_builtin_db(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg) { R[A] = 0; return 0; } + case WO_B_DB_SCAN: { + uint32_t cid = (uint32_t)R[B]; + if (cid >= db->class_cnt) { + *msg = "no such class"; + return WO_T_DB; + } + wo_multi *ids = wo_multi_new(&vm->rt, WO_K_SCALAR); + if (!ids) return WO_T_OOM; + /* materialize the id list up front — the 9b cursor-stability rule: + * the loop body then point-reads each id, so a row updated mid-loop + * (even an indexed column) cannot disturb the iteration */ + db_table *t = &db->tables[cid]; + if (t->row_size) { + uint32_t total = t->slab_cnt * DB_SLAB_ROWS; + for (uint32_t g = 0; g < total; g++) { + if (!(t->bitmap[g >> 6] & (1ull << (g & 63)))) continue; + db_row *row = + (db_row *)(t->slabs[g / DB_SLAB_ROWS] + (size_t)(g % DB_SLAB_ROWS) * t->row_size); + if (wo_multi_push(ids, row->id) != 0) return WO_T_OOM; + } + } + R[A] = (uint64_t)(uintptr_t)ids; + return 0; + } + case WO_B_DB_GET_FIELD: { + uint32_t cid = (uint32_t)R[B]; + uint64_t id = R[B + 1]; + uint32_t field = (uint32_t)R[B + 2]; + if (cid >= db->class_cnt || field >= db->classes[cid].field_cnt) { + *msg = "no such field"; + return WO_T_DB; + } + db_row *row = wo_row_ptr(db, cid, id); + if (!row) { + *msg = "no such row"; + return WO_T_DB; + } + int ok = 1; + uint64_t v = wo_val_decode_vm(db, &vm->rt, db->classes[cid].kinds[field], + row->slots[field], &ok, msg); + if (!ok) return WO_T_OOM; + R[A] = v; + return 0; + } + case WO_B_DB_PROBE: { + uint32_t cid = (uint32_t)R[B]; + uint32_t index = (uint32_t)R[B + 1]; + if (cid >= db->class_cnt) { + *msg = "no such class"; + return WO_T_DB; + } + wo_multi *ids = wo_multi_new(&vm->rt, WO_K_SCALAR); + if (!ids) return WO_T_OOM; + db_table *t = &db->tables[cid]; + if (t->row_size && index < t->index_cnt) { + db_index *ix = &t->indexes[index]; + uint32_t col = ix->cols[0]; + uint8_t kind = db->classes[cid].kinds[col]; + uint64_t key = R[B + 2]; + uint32_t total = t->slab_cnt * DB_SLAB_ROWS; + for (uint32_t g = 0; g < total; g++) { + if (!(t->bitmap[g >> 6] & (1ull << (g & 63)))) continue; + db_row *row = + (db_row *)(t->slabs[g / DB_SLAB_ROWS] + (size_t)(g % DB_SLAB_ROWS) * t->row_size); + int eq; + if (kind == WO_K_TEXT) { + const wo_str *want = (const wo_str *)(uintptr_t)key; + const db_text *have = (const db_text *)(uintptr_t)row->slots[col]; + eq = (!want && !have) || + (want && have && want->len == have->len && + memcmp(want->data, have->bytes, have->len) == 0); + } else + eq = row->slots[col] == key; + if (eq && wo_multi_push(ids, row->id) != 0) return WO_T_OOM; + } + } + R[A] = (uint64_t)(uintptr_t)ids; + return 0; + } default: *msg = "unknown db builtin"; return WO_T_DB; diff --git a/database/src/table.c b/database/src/table.c index feaf104..cf2b348 100644 --- a/database/src/table.c +++ b/database/src/table.c @@ -610,6 +610,12 @@ void wo_db_val_free(wo_db *db, uint8_t kind, uint64_t v) { db_val_free(kind, v); } +uint64_t wo_val_decode_vm(wo_db *db, wo_rt *rt, uint8_t kind, uint64_t engine_val, + int *ok, const char **msg) { + (void)db; + return db_val_decode(rt, kind, engine_val, ok, msg); +} + int wo_row_update_field(wo_db *db, uint32_t class_id, uint64_t id, uint32_t field, uint64_t vm_val, const char **msg, int *err_kind) { if (err_kind) *err_kind = DB_ERR_MISC; diff --git a/database/src/table.h b/database/src/table.h index c99e0d0..80805b8 100644 --- a/database/src/table.h +++ b/database/src/table.h @@ -174,6 +174,11 @@ db_row *wo_row_create_raw(wo_db *db, uint32_t class_id, uint64_t id); * decode error paths). */ void wo_db_val_free(wo_db *db, uint8_t kind, uint64_t v); +/* Decode one engine slot value to a FRESH VM value in [rt] (the out-gate: + * always a copy). The query builtins' field reads go through this. */ +uint64_t wo_val_decode_vm(wo_db *db, wo_rt *rt, uint8_t kind, uint64_t engine_val, + int *ok, const char **msg); + /* Engine-internal, replay only: after wal.c fills a raw row's slots, this * runs the index maintenance the normal insert runs inline — including the * unique check, whose violation during replay is corruption, not data diff --git a/runtime/src/builtin.c b/runtime/src/builtin.c index 019a99c..659ef56 100644 --- a/runtime/src/builtin.c +++ b/runtime/src/builtin.c @@ -70,7 +70,7 @@ int wo_builtin(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg) { if (C == WO_B_JSON_ENCODE || C == WO_B_JSON_DECODE) return wo_builtin_json(vm, R, ins, msg); if (C >= WO_B_SYS_FIRST && C <= WO_B_PROC_RUN) return wo_builtin_sys(vm, R, ins, msg); - if (C >= WO_B_DB_INSERT && C <= WO_B_DB_DELETE) return wo_builtin_db(vm, R, ins, msg); + if (C >= WO_B_DB_INSERT && C <= WO_B_DB_PROBE) return wo_builtin_db(vm, R, ins, msg); switch (C) { case WO_B_NOW: { /* wall-clock milliseconds */ struct timespec ts; diff --git a/runtime/src/loader.c b/runtime/src/loader.c index 81da8d5..33ed884 100644 --- a/runtime/src/loader.c +++ b/runtime/src/loader.c @@ -44,6 +44,9 @@ static const uint8_t b_arity[WO_B_MAX + 1] = { [WO_B_DB_INSERT] = 1, [WO_B_DB_UPDATE_FIELD] = 4, [WO_B_DB_DELETE] = 2, + [WO_B_DB_SCAN] = 1, + [WO_B_DB_GET_FIELD] = 3, + [WO_B_DB_PROBE] = 3, [WO_B_NOW] = 0, [WO_B_PRINT] = 1, [WO_B_PRINT_INT] = 1, [WO_B_WORDS] = 1, [WO_B_MULTI_NEW] = 0, [WO_B_MULTI_PUSH] = 2, [WO_B_MULTI_GET] = 2, [WO_B_COUNT] = 1, [WO_B_LATEST] = 1, diff --git a/runtime/src/wob.h b/runtime/src/wob.h index 4146cbd..8b0d427 100644 --- a/runtime/src/wob.h +++ b/runtime/src/wob.h @@ -302,8 +302,23 @@ enum { /* DB_DELETE: R[B] = class id, R[B+1] = row id. R[A] = 0. A missing row * traps WO_T_DB (deleting what is not there is a fault, not a no-op). */ WO_B_DB_DELETE = 63, + /* the query surface's reads (iteration 9b). A table-class value IS its + * row id at runtime (the "objects are rows" model), so these are how the + * compiled query loop touches storage: + * DB_SCAN (64): R[B] = class -> R[A] = multi of every id + * DB_GET_FIELD(65): R[B]=class, R[B+1]=id, R[B+2]=field + * -> R[A] = that field, decoded to a VM value (a + * Text field decodes to a fresh Text; a ref field + * decodes to the target id). Missing row traps + * WO_T_DB. + * DB_PROBE (66): R[B]=class, R[B+1]=index, R[B+2]=key + * -> R[A] = multi of ids whose first indexed + * column equals key (backlink + indexed where). */ + WO_B_DB_SCAN = 64, + WO_B_DB_GET_FIELD = 65, + WO_B_DB_PROBE = 66, }; -#define WO_B_MAX 63u +#define WO_B_MAX 66u /* ids at or above this one live in sysio.c, not builtin.c */ #define WO_B_SYS_FIRST WO_B_FS_EXISTS