Skip to content

Top-level @service results: field fetchers silently return null for columns the record does not carry (no post-fetch, unlike nested @table fields) #534

Description

@krhoybraten-sikt

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:

  1. Top-level @service results get the same key-based post-fetch as nested @table fields, or
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions