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:
--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
--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:
--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
create table "Person" ( id int primary key, name text);create table "Address"( id int primary key, "isPrimary" bool not null default false, "personId" int references "Person"(id), address text);-- Example computed relationcreate function "primaryAddress"("Person") returns setof "Address" rows 1 language sql as$$ select addr from "Address" addr where $1.id = addr."personId" and addr."isPrimary" limit 1$$;insert into "Person"(id, name)values (1, 'Foo Barington');insert into "Address"(id, "isPrimary", "personId", address)values (4, true, 1, '1 Main St.');results in the GraphQL type
type Person implements Node { """Globally Unique Record Identifier""" nodeId: ID! ... primaryAddress: Address}and can be queried like a natively enforced relationship
{ personCollection { edges { node { id name primaryAddress { address } } } }}To-Many#
To-many relationships can be defined using a function that returns a setof <entity>
For example:
create table "Person" ( id int primary key, name text);create table "Address"( id int primary key, address text);create table "PersonAtAddress"( id int primary key, "personId" int not null, "addressId" int not null);-- Computed relation to bypass "PersonAtAddress" table for cleaner APIcreate function "addresses"("Person") returns setof "Address" language sql as$$ select addr from "PersonAtAddress" pa join "Address" addr on pa."addressId" = "addr".id where pa."personId" = $1.id$$;insert into "Person"(id, name)values (1, 'Foo Barington');insert into "Address"(id, address)values (4, '1 Main St.');insert into "PersonAtAddress"(id, "personId", "addressId")values (2, 1, 4);results in the GraphQL type
type Person implements Node { """Globally Unique Record Identifier""" nodeId: ID! ... addresses( first: Int last: Int before: Cursor after: Cursor filter: AddressFilter orderBy: [AddressOrderBy!] ): AddressConnection}and can be queried like a natively enforced relationship
{ personCollection { edges { node { id name addresses { edges { node { id address } } } } } }}