Summary
Field fetchers for a @table type returned from a top-level @service query field read columns directly off the record the service returned. If the service populated only the key columns, every non-key field resolves to silent null — no warning at build time, no error at runtime. The same shape nested deeper in a service result behaves differently: there, remaining fields are post-fetched in batch through a DataLoader keyed on the primary key, so key-only records work.
The asymmetry itself may be a deliberate contract ("a top-level service owns its record content"), but the failure mode when the contract is broken is the problem: the generated field fetcher quietly returns null for a column the record never carried, for the entire result set. This is the second time this class of defect has reached one of our subgraphs, and both times it survived review because everything compiles and nothing throws.
Minimal repro
Schema (tilgangsstyring subgraph, graphitron 10.0.0-RC33):
type Query {
tildelbareTilgangskoder: [Tilgangsrolle]
@service(service: {className: "…Tilgangsroller", method: "tildelbare"})
}
type Tilgangsrolle @table(name: "rolle") {
kode: String! @field(name: "ROLLEKODE")
beskrivelse: String
rolletype: Rolletype
}
Service returns RolleRecords with only ROLLEKODE set (the natural thing to do when a routine yields the key set and the docs elsewhere say remaining fields are fetched from the key):
Result: kode/id correct for all rows; beskrivelse and rolletype are null for all rows (observed: 25/25 in our catalog). The identical Tilgangsrolle shape reached through a nested @table field in a mutation payload resolves all fields, via the generated DataLoader batch fetch.
Expected
One of:
- Top-level
@service results get the same key-based post-fetch as nested @table fields, or
- A field fetcher that reads a column the returned record does not carry fails loudly — ideally at generation time (the generator knows which columns the field fetchers will read; it could require the service contract to declare them), at minimum at runtime.
Option 2 seems strictly safer: silent null across a whole result set looks exactly like an empty column and ships to production.
Environment
graphitron 10.0.0-RC33 (also inspected the RC34 generated code — same shape), PostgreSQL 18, jOOQ Pro 3.20.11.
Summary
Field fetchers for a
@tabletype returned from a top-level@servicequery field read columns directly off the record the service returned. If the service populated only the key columns, every non-key field resolves to silentnull— no warning at build time, no error at runtime. The same shape nested deeper in a service result behaves differently: there, remaining fields are post-fetched in batch through a DataLoader keyed on the primary key, so key-only records work.The asymmetry itself may be a deliberate contract ("a top-level service owns its record content"), but the failure mode when the contract is broken is the problem: the generated field fetcher quietly returns
nullfor a column the record never carried, for the entire result set. This is the second time this class of defect has reached one of our subgraphs, and both times it survived review because everything compiles and nothing throws.Minimal repro
Schema (tilgangsstyring subgraph, graphitron 10.0.0-RC33):
Service returns
RolleRecords with onlyROLLEKODEset (the natural thing to do when a routine yields the key set and the docs elsewhere say remaining fields are fetched from the key):Result:
kode/idcorrect for all rows;beskrivelseandrolletypearenullfor all rows (observed: 25/25 in our catalog). The identicalTilgangsrolleshape reached through a nested@tablefield in a mutation payload resolves all fields, via the generated DataLoader batch fetch.Expected
One of:
@serviceresults get the same key-based post-fetch as nested@tablefields, orOption 2 seems strictly safer: silent
nullacross a whole result set looks exactly like an empty column and ships to production.Environment
graphitron 10.0.0-RC33 (also inspected the RC34 generated code — same shape), PostgreSQL 18, jOOQ Pro 3.20.11.