feat(database): query-read engine builtins (iteration 9b foundation)
- DB_SCAN(64): class -> multi<Int> of every row id, materialized up front (the 9b cursor-stability rule: the loop body point-reads, so a row updated mid-loop cannot disturb iteration) - DB_GET_FIELD(65): class,id,field -> the field decoded to a fresh VM value (the out-gate copy); a table-class value IS its row id at runtime, so this is how a compiled query reads a column, and a ref field decodes to the target id for navigation - DB_PROBE(66): class,index,key -> multi<Int> of ids whose first indexed column equals key (backlink + indexed where) - wo_val_decode_vm wrapper exposed; dispatch range 61..66, loader arities, runner mirror updated - 15 runtime suites green, oop-e2e 73/0 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
71d3985e81
commit
048c242a01
7 changed files with 117 additions and 3 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
#include "db.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<Int> 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<Int> 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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue