Computed Fields
Using Postgres Computed Fields with GraphQL.
Computed Values#
PostgreSQL Builtin (Preferred)#
PostgreSQL has a builtin method for adding generated columns to tables. Generated columns are reflected identically to non-generated columns. This is the recommended approach to adding computed fields when your computation meets the restrictions. Namely:
- expression must be immutable
- expression may only reference the current row
For example:
1--8<-- "test/expected/extend_type_with_generated_column.out"Extending Types with Functions#
For arbitrary computations that do not meet the requirements for generated columns, a table's reflected GraphQL type can be extended by creating a function that:
- accepts a single argument of the table's tuple type
1--8<-- "test/expected/extend_type_with_function.out"If the function is written in SQL, its volatility can impact freshness of data returned in mutations:
1--8<-- "test/expected/issue_337.out"Computed Relationships#
Computed relations can be helpful to express relationships:
- between entities that don't support foreign keys
- too complex to be expressed via a foreign key
If the relationship is simple, but involves an entity that does not support foreign keys e.g. Foreign Data Wrappers / Views, defining a comment directive is the easiest solution. See the view doc for a complete example. Note that for entities that do not support a primary key, like views, you must define one using a comment directive to use them in a computed relationship.
Alternatively, if the relationship is complex, or you need compatibility with PostgREST, you can define a relationship using set returning functions.
To-One#
To One relationships can be defined using a function that returns setof <entity> rows 1
For example
1create table "Person" (2 id int primary key,3 name text4);56create table "Address"(7 id int primary key,8 "isPrimary" bool not null default false,9 "personId" int references "Person"(id),10 address text11);1213-- Example computed relation14create function "primaryAddress"("Person")15 returns setof "Address" rows 116 language sql17 as18$$19 select addr20 from "Address" addr21 where $1.id = addr."personId"22 and addr."isPrimary"23 limit 124$$;2526insert into "Person"(id, name)27values (1, 'Foo Barington');2829insert into "Address"(id, "isPrimary", "personId", address)30values (4, true, 1, '1 Main St.');results in the GraphQL type
1type Person implements Node {2 """Globally Unique Record Identifier"""3 nodeId: ID!4 ...5 primaryAddress: Address6}and can be queried like a natively enforced relationship
1{2 personCollection {3 edges {4 node {5 id6 name7 primaryAddress {8 address9 }10 }11 }1213 }14}To-Many#
To-many relationships can be defined using a function that returns a setof <entity>
For example:
1create table "Person" (2 id int primary key,3 name text4);56create table "Address"(7 id int primary key,8 address text9);1011create table "PersonAtAddress"(12 id int primary key,13 "personId" int not null,14 "addressId" int not null15);161718-- Computed relation to bypass "PersonAtAddress" table for cleaner API19create function "addresses"("Person")20 returns setof "Address"21 language sql22 as23$$24 select25 addr26 from27 "PersonAtAddress" pa28 join "Address" addr29 on pa."addressId" = "addr".id30 where31 pa."personId" = $1.id32$$;3334insert into "Person"(id, name)35values (1, 'Foo Barington');3637insert into "Address"(id, address)38values (4, '1 Main St.');3940insert into "PersonAtAddress"(id, "personId", "addressId")41values (2, 1, 4);results in the GraphQL type
1type Person implements Node {2 """Globally Unique Record Identifier"""3 nodeId: ID!4 ...5 addresses(6 first: Int7 last: Int8 before: Cursor9 after: Cursor10 filter: AddressFilter11 orderBy: [AddressOrderBy!]12 ): AddressConnection13}and can be queried like a natively enforced relationship
1{2 personCollection {3 edges {4 node {5 id6 name7 addresses {8 edges {9 node {10 id11 address12 }13 }14 }15 }16 }17 }18}