Issue #477: dsl … -> T { … } eod: a block’s rows are a declared struct, matched by name, strictly, NULL only into T? (ADR-299 D15-D19)

Open and answer this issue on GitHub.

blocked · opened 2026-10-06 · updated 2026-10-06 · 0 comments

How it came up

#114 asked how a dsl block’s row type is made. ADR-323 answered how a grammar describes a type, and ADR-299 D12-D14 answered how the compiler-written row’s fields are named.

The owner then asked why a program cannot simply say which of its own structs a row is. The compiler-written row is a type of its own, and Nikaia’s structs are nominal (tried: a Person where a Contact is expected is NK1102).

Decisions (owner, one at a time)

Records:

To build

Prerequisite: meta::column and the compiler-written row (ADR-299 D2, ADR-323), which are not built yet.

  1. Parser: -> Type after a dsl block’s build-time arguments and before {. Without arguments: dsl sqlite -> T { … } eod.
  2. Checker, the target:
    • T must be a struct the program declares; else NK1218.
    • Each field’s type must be a scalar of ADR-323 D1, its T?, or a struct (a group); else NK1218, also for Shared[T].
  3. Checker, matching (from the grammar’s declared columns, which carry their table or alias):
    • A scalar field matches the flat column of its name. A name two tables’ columns share is NK1222, with help: AS, a group field, no -> T.
    • A struct field named like an alias or table matches that table’s columns, recursively, with the same rules.
    • A column with no field is NK1219, unless extra_columns: true.
    • A field with no column is NK1220, unless missing_fields: true and the field is T?.
    • A Maybe(T) column into a non-? field, or a type that is not the field’s, is NK1221, with help: T?, COALESCE(…), WHERE … IS NOT NULL.
  4. Build-time arguments:
    • extra_columns and missing_fields (both bool, default false) are taken by the compiler from a block with -> T and not passed to the grammar.
    • A grammar that declares either is NK1223.
  5. Conversion (D8):
    • Writes the T directly.
    • A NULL for a non-? field fails execute with an error naming the field, the column and the table.
    • A missing T? field under missing_fields is null.
  6. Tests (with a stub grammar that declares fixed columns, so no SQL is needed):
    • -> Contact with email, name in reverse order builds, and the rows are Contact (pass one to fn send(c: Contact)).
    • Each of NK1218, NK1219, NK1220, NK1221, NK1222, NK1223 once, with its help.
    • extra_columns: true builds past an extra column.
    • missing_fields: true with a T? field gives null; with a non-? field it is still NK1220.
    • A group field a: Account beside b: Bank with a.id and b.id builds without AS.
    • At run time, a stub driver returning NULL for a NOT NULL-declared column: execute fails with the named error and does not crash.
    • Without -> T, extra_columns: true is refused by the grammar as an unknown argument (ADR-299 D3).

Back to Open Issues