Skip to content

fix(graphile-build-pg): support nested relational polymorphism - #3217

Open
dilame wants to merge 4 commits into
graphile:mainfrom
dilame:fix/nested-relational-polymorphism
Open

dilame wants to merge 4 commits into
graphile:mainfrom
dilame:fix/nested-relational-polymorphism

Conversation

@dilame

@dilame dilame commented Oct 15, 2026

Copy link
Copy Markdown

Closes #3208.

Stacked on #3213 (@ref on a relational root). This branch contains that PR's two commits; the last two commits are this PR. Review with git diff -w against #3213's head: about 550 raw diff lines are re-indentation from wrapping two loops in a fixpoint loop (-w: +259 / -28 in PgPolymorphismPlugin.ts).

Problem

See #3208: a relational polymorphic table that is one of the @types of another relational polymorphic table makes the schema build fail (Interface field Truck.type expected but BoxTruck does not provide it).

Change (PgPolymorphismPlugin)

  • The attribute copy (pgRegistry_PgRegistryBuilder_finalize) and the relation/ref copy (pgRegistry_PgRegistry) repeat until nothing changes, so a root's attributes, relations and refs reach its leaves regardless of visiting order. An attribute is marked identicalVia only when it is the implementation's own column (one copied on an earlier pass already has via).
  • The relation from a nested root back to its own root is not copied to the nested root's implementations.
  • GraphQLObjectType_interfaces and a new GraphQLInterfaceType_interfaces hook declare the interface of every root above the type, transitively.
  • When the structure is nested, planType reads the type attribute of each root on the way down (through the relations from the base record) and resolves the row to its leaf type; planForType follows the relations to the leaf. For non-nested structures the existing code path is used.

Performance: for a nested structure, resolving the type reads the discriminator of each nested table (one extra join per level). Non-nested structures are unchanged.

Commits (this PR)

  1. test(graphile-build-pg): nested_polymorphism fixture (vehicles -> cars / trucks -> box_trucks / tankers, a relation on the root and one on the middle table) and __tests__/queries/polymorphic/nested.test.graphql with snapshots: reads through Vehicle, Truck and the leaf types with fragments at every level. On fix(graphile-build-pg): carry a relational root's refs to its implementations #3213's head:
    ● expect(errors).toBeFalsy()
      AggregateError: Schema construction failed due to 6 validation failure(s). First failure was: Interface field Truck.type expected but BoxTruck does not provide it.
    Tests: 15 failed, 15 total
    
  2. fix(graphile-build-pg): the change above + changeset. Tests: 15 passed, 15 total.

Verification

Run on this branch against PostgreSQL 17 (UTF8): postgraphile/postgraphile full jest: 237 passed, 237 total, 818 passed, 818 total; yarn test:schema-exports: passes; EXPORT_SCHEMA=typeDefs jest __tests__/queries __tests__/mutations: 744 passed, 744 total; EXPORT_SCHEMA=graphql-js jest __tests__/queries/polymorphic __tests__/queries/refs: 177 passed, 177 total; graphile-build-pg yarn test: passes. prettier --check and eslint on the changed files: clean. Baseline on main with the same setup: 235 suites / 792 tests, typeDefs 718, graphql-js 151, all passing. No existing snapshot changes.

#3215 edits the same relation-copy loop that this PR re-indents; whichever merges second needs a trivial rebase.

Add a `relational_root_refs` fixture (a relational polymorphic root with
a `@ref` to another table) and a query test that reads the ref through
the interface and through each implementation. On main the schema does
not build: `Interface field Item.reviews expected but Book does not
provide it`.
…ntations

A `@ref` on the root of a relational polymorphic table becomes a field of
the root's interface, but it was not copied to the implementations (only
the root's relations were), so the schema build failed with
`Interface field <Root>.<ref> expected but <Variant> does not provide it`.

Copy the root's refs to each implementation, reached via the shared
relation to the root, as is already done for the root's relations.
Add a `nested_polymorphism` fixture (a relational polymorphic table whose
implementation is itself a relational polymorphic table) and a query
test that reads it through each interface and through the leaf types.
On main the schema does not build: `Interface field Truck.kind expected
but BoxTruck does not provide it`.
A relational polymorphic table that is itself one of the types of another
relational polymorphic table made the schema build fail (for example
`Interface field Truck.kind expected but BoxTruck does not provide it`):
attributes, relations and refs were only copied one level down, the leaves
did not implement the outer interface, and a row was resolved using only
the outermost type attribute.

- Repeat the copy of attributes, relations and refs until nothing changes,
  so that a root's reach the leaves whatever order the tables are visited.
- Only mark an attribute `identicalVia` when it is the implementation's own
  column, not one a previous pass copied there.
- Every implementation (and every nested interface) implements the
  interface of each table above it.
- Resolve a row to its leaf type by reading the type attribute of each
  root on the way down, and plan the leaf by following the relations to it.
@changeset-bot

changeset-bot Bot commented Oct 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4ab26b5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
graphile-build-pg Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nested relational polymorphism: schema build fails

1 participant