- `woc` now emits `.wob` that `wovm` runs: emit.ml lowers the typed, owner-annotated AST (scope-stack registers with a >64 WO-E401 diagnostic, Lua-style call windows, ICALL by slot, dedup const pool, drop maps, line tables, implicit terminators); disasm.ml backs `--dump-bc` goldens. - Ownership lowering consumes the four owner tables verbatim; RESIDUAL is the only source of borrow ops, coalesced per operand. Review caught the emitter consuming only 2 of owner.ml's 4 residual producers — an assignment-anchored aliasing violation ran to exit 0 instead of trapping; fixed, plus a backstop raising WO-E404 for any residual region left unconsumed. - Conformance harness `scripts/oop-e2e.sh` (`just oop-e2e`): four fixture kinds with exact outcomes — byte-exact stdout, one WO-E### anchored on `error CODE:`, numeric trap code, gc trace. 25 fixtures incl. pricing-demo logic, the ownership suite, and DB_STUB's parse-but-trap. `tests/` un-ignored so the corpus is actually tracked. - `woc build` produces a self-contained binary: wovm copy + appended image + 20-byte trailer, self-exec via /proc/self/exe. Verified relocated outside the repo, argless, and against adversarial trailer corruption. - Milestone 1's five spec criteria all MET (`just oop-accept`). Criterion 3 closed by WO-E405 — the entry must return `Int`, since program mode already says its return value is the exit code — which deletes the leak class without adding return-type metadata to the format. `gc/held-cycle` retired: an externally-held cycle is not expressible in a post-exit pump. - New spec: inferred GC + incremental per-shard tri-color mark-sweep, retiring `@gc` and reference counting. Story gains iterations 7b (that work) and 9b (`@table`, relations, compiler-checked query); `.dev/reference` gains a sparse System.Linq checkout. Priority: 5→6→7 (log-watcher) then 7b, 8, 9, 9b.
132 lines
6.8 KiB
Markdown
132 lines
6.8 KiB
Markdown
# Iteration 9b — `@table`, relations, and language-integrated query
|
|
|
|
> Format: `product/story-iteration-template`. Part of
|
|
> [Story — one language, one runtime, one database, one binary](00-story.md).
|
|
>
|
|
> **Inserted 2026-08-11**, hence `9b` rather than a renumber. It follows
|
|
> iteration 9 because a query surface needs tables that actually execute, and
|
|
> precedes iteration 10 because `service` blocks will want to return query
|
|
> results.
|
|
>
|
|
> **No spec exists yet.** This iteration frames the outcome and records the
|
|
> open questions; the design must be brainstormed before a plan is written.
|
|
> The three questions in *Info* are genuine forks, not details.
|
|
|
|
## Goals
|
|
|
|
- `@table` graduates from a parsed-but-inert annotation into the declaration
|
|
that makes a class persistent: named storage, declared indexes, and a
|
|
primary identity.
|
|
- Relations become first-class and typed — `ref T` foreign keys, `backlink`
|
|
inverses, and `multi` collections — so a developer navigates their data by
|
|
following fields rather than by hand-writing joins.
|
|
- Queries are **written in the language, checked by the compiler**: a
|
|
LINQ-shaped operator vocabulary (filter, project, join, group, order,
|
|
aggregate) over tables and relations, with the result's type inferred and
|
|
every column reference resolved at compile time. A typo in a field name is
|
|
a compile error, not a runtime one.
|
|
|
|
## Acceptance Criteria
|
|
|
|
- What to achieve?
|
|
- **Given** a class annotated `@table` with a declared index,
|
|
- **when** the program is compiled and run,
|
|
- **then** its instances persist through the engine, the index is built,
|
|
and a query that could use the index does use it — demonstrated, not
|
|
assumed.
|
|
- What to achieve?
|
|
- **Given** two classes related by `ref` with a `backlink` inverse,
|
|
- **when** a query navigates the relation in either direction,
|
|
- **then** it typechecks with the related class's field set in scope, and
|
|
navigating a field that does not exist is a compile error naming it.
|
|
- What to achieve?
|
|
- **Given** a query whose result shape is a projection rather than a whole
|
|
row,
|
|
- **when** it is assigned or returned,
|
|
- **then** its type is the projected shape — so a later use of a column
|
|
the projection dropped is a compile error.
|
|
- What to achieve?
|
|
- **Given** a query written against tables,
|
|
- **when** the compiler lowers it,
|
|
- **then** it becomes engine operations, **not** a string handed to a
|
|
parser at runtime — provable by disassembly, and by the absence of any
|
|
SQL-text construction in the emitted image.
|
|
- What to achieve?
|
|
- **Given** the ecommerce sample's existing relational shapes
|
|
(`Order.user: ref User`, `User.orders: backlink Order.user`, line-item
|
|
collections),
|
|
- **when** they are expressed as queries in this surface,
|
|
- **then** each produces the same results as the equivalent hand-written
|
|
query, and the sample's README records anything that could not be
|
|
expressed.
|
|
|
|
## Out Of Scope
|
|
|
|
- Cross-shard queries and distributed joins — iteration 8 owns ownership
|
|
movement, and a query spanning shards is a 2PC concern recorded with the
|
|
database track.
|
|
- `LIVE` subscriptions over queries. The subscription registry is the
|
|
HTTP/UI track's; a query that pushes updates is a later composition of the
|
|
two.
|
|
- Migrations. Changing a `@table` class's shape is the blue-green spec's
|
|
additive-only differ (iteration 12), not this iteration's problem.
|
|
- Query optimisation beyond index selection. A cost-based planner is a
|
|
separate, much later concern; this iteration must only prove that declared
|
|
indexes are used.
|
|
|
|
## Info
|
|
|
|
Three open forks the spec has to settle. Each is a real decision, and I have a
|
|
leaning on all three but no mandate.
|
|
|
|
**1. Where does this leave the existing SQL + Cypher query layer?**
|
|
`docs/runtime/database/02-wo-language.md` specifies a two-layer design — a
|
|
schema layer plus a query layer of literal SQL and Cypher with five
|
|
"fixed-glue" rules. A language-integrated surface either replaces that layer,
|
|
sits beside it, or becomes the only surface with SQL retained purely as an
|
|
export format. Replacing it is the coherent choice and also the most
|
|
disruptive, because that document is normative and the `wo-db` C++ prototype
|
|
implements the SQL/Cypher grammar it describes.
|
|
|
|
**2. There are no function values, so what is the syntax?**
|
|
LINQ-to-Objects is built on delegates: `.Where(x => x.Age > 18)` passes a
|
|
lambda. writeonce has **no function-value type**, and the systems-track spec
|
|
deliberately rejected closure builtins (`map`/`filter`/`reduce`) for exactly
|
|
this reason. So the surface cannot be method-chaining-with-lambdas as written
|
|
in C#. The realistic options are a comprehension syntax the compiler desugars
|
|
(`from o in orders where o.total > 100 select o.id`), or method chaining whose
|
|
"lambda" argument is a compiler-recognised expression form rather than a
|
|
value. Either way the predicate is **compile-time syntax, never a runtime
|
|
closure** — which is also what lets the whole query lower to engine ops.
|
|
|
|
**3. What does the reference actually contribute?**
|
|
`.dev/reference/dotnet-runtime/src/libraries/System.Linq/src/System/Linq/`
|
|
(sparse checkout, added with this iteration) is the operator catalogue: read
|
|
`Where.cs`, `Select.cs`, `Join.cs`, `GroupBy.cs`, `OrderBy.cs` for what each
|
|
operator means and which edge cases it has to answer, and the `*.SpeedOpt.cs`
|
|
files for how LINQ specialises when the source's shape is known statically —
|
|
directly relevant, since writeonce knows every shape statically. What does
|
|
**not** transfer is the machinery: `IEnumerable` iterator composition,
|
|
delegates, and `IQueryable`'s runtime expression trees, the last of which
|
|
depends on reflection that principle 13 forbids outright. Take the vocabulary
|
|
and the semantics; leave the plumbing.
|
|
|
|
Also relevant: `@table(name:, index:)` already parses today with known-key
|
|
validation (`WO-E102`), the Rust runtime already ships secondary indexes and
|
|
`find_by` behind that annotation, and `ref T` already classifies as a scalar
|
|
id rather than a pointer — so the relational vocabulary partly exists and this
|
|
iteration makes it mean something in the C stack.
|
|
|
|
## Proposed Solution
|
|
|
|
- **Brainstorm a spec first**, settling the three forks above; only then write
|
|
the plan. This iteration deliberately ships no plan pointer, because
|
|
choosing between "replace the SQL layer" and "sit beside it" changes what
|
|
the plan contains.
|
|
- Study `.dev/reference/dotnet-runtime`'s `System.Linq` operator set for the
|
|
vocabulary, and `docs/runtime/database/02-wo-language.md` plus
|
|
`prototypes/wo-db/` for the semantics already committed to.
|
|
- Expect the work to span the front end (query syntax, relation typing,
|
|
projection types), the emitter (lowering to engine operations rather than
|
|
text), and the engine (index selection, relation traversal) — which is why
|
|
it follows iteration 9 rather than preceding it.
|