Summary
PostgreSQL computed columns are dropped from the GraphQL schema when its return type is a domain over a composite type. No error, the field just doesn't appear. The same function returning the underlying composite
directly works. Domain columns of the same domain type work fine.
Steps to reproduce
Schema:
create type money_raw as (amount numeric(10,2), currency_code text);
create domain money as money_raw check (length((value).currency_code) = 3);
create table thing (
id serial primary key,
net_amount money,
tax_amount money
);
create function thing_total_amount(t thing) returns money as $$
select case
when t.net_amount is null or t.tax_amount is null then null::money
else row((t.net_amount).amount + (t.tax_amount).amount,
(t.net_amount).currency_code)::money
end
$$ language sql stable;
create function thing_total_amount_raw(t thing) returns money_raw as $$
select row((t.net_amount).amount + (t.tax_amount).amount,
(t.net_amount).currency_code)::money_raw
$$ language sql stable;
Build the schema and inspect the Thing type.
Expected results
Thing.totalAmount: Money — the computed column should be exposed using the same GraphQL output type (Money) that the domain columns already use.
Actual results
thing_total_amount (returns the domain money) → no totalAmount field at all.
thing_total_amount_raw (returns the composite money_raw) → field appears, typed as a separate MoneyRaw object type.
- Scalars and domain-over-scalar return types work as expected.
Additional context
postgraphile@5.0.3, graphile-build-pg@5.0.2, @dataplan/pg@1.0.3, grafast@1.0.2, graphile-build@5.0.2, graphql@16.14.0
- Node 22, schema built via
makeSchema
- No custom behavior tags involved
Root cause (traced in graphile-build-pg@5.0.2/dist/plugins/PgProceduresPlugin.js):
@dataplan/pg's domainOfCodec builds the domain codec by spreading the inner composite codec (...innerCodec), so the domain codec inherits .attributes from money_raw (and additionally carries domainOfCodec). In PgProceduresPlugin, the resource-building branch is gated only on returnCodec.attributes || returnCodec.arrayOfCodec?.attributes, so the domain-over-composite return is routed into the composite/table-return branch. That branch resolves the backing pg_class from the return type's typrelid:
const pgClass = await info.helpers.pgIntrospection.getClass(serviceName, pgType.typrelid);
if (!pgClass) return null; // resource silently dropped
A domain's pg_type.typrelid is 0, so getClass returns null and the entire function resource is dropped. (For comparison: a bare composite has a real typrelid, so its resource builds — but it then registers/uses a separate MoneyRaw object type rather than the domain's Money.)
Domain columns are unaffected because PgTablesPlugin registers the Money output type for the domain codec (keyed by codec identity) and PgAttributesPlugin resolves columns through that registration.
Possible Solution
The domain-over-composite return should not be treated as a table/composite return; it should keep its own (domain) codec, which already has a registered output type. Excluding domain codecs from the composite branch so they fall through to the scalar/else branch (which builds the resource with codec: returnCodec) fixes it, the field is then exposed using the domain's already-registered Money type, and no duplicate MoneyRaw type is created:
if (!returnCodec.isAnonymous &&
+ !returnCodec.domainOfCodec &&
(returnCodec.attributes || returnCodec.arrayOfCodec?.attributes)) {
Verified against graphile-build-pg@5.0.2: with this one-line change, a computed column returning a domain-over-composite is exposed using the same GraphQL type as the domain columns (Money), and the stray MoneyRaw type disappears.
Summary
PostgreSQL computed columns are dropped from the GraphQL schema when its return type is a domain over a composite type. No error, the field just doesn't appear. The same function returning the underlying composite
directly works. Domain columns of the same domain type work fine.
Steps to reproduce
Schema:
Build the schema and inspect the
Thingtype.Expected results
Thing.totalAmount: Money— the computed column should be exposed using the same GraphQL output type (Money) that the domain columns already use.Actual results
thing_total_amount(returns the domainmoney) → nototalAmountfield at all.thing_total_amount_raw(returns the compositemoney_raw) → field appears, typed as a separateMoneyRawobject type.Additional context
postgraphile@5.0.3,graphile-build-pg@5.0.2,@dataplan/pg@1.0.3,grafast@1.0.2,graphile-build@5.0.2,graphql@16.14.0makeSchemaRoot cause (traced in
graphile-build-pg@5.0.2/dist/plugins/PgProceduresPlugin.js):@dataplan/pg'sdomainOfCodecbuilds the domain codec by spreading the inner composite codec (...innerCodec), so the domain codec inherits.attributesfrommoney_raw(and additionally carriesdomainOfCodec). InPgProceduresPlugin, the resource-building branch is gated only onreturnCodec.attributes || returnCodec.arrayOfCodec?.attributes, so the domain-over-composite return is routed into the composite/table-return branch. That branch resolves the backingpg_classfrom the return type'styprelid:A domain's
pg_type.typrelidis0, sogetClassreturns null and the entire function resource is dropped. (For comparison: a bare composite has a realtyprelid, so its resource builds — but it then registers/uses a separateMoneyRawobject type rather than the domain'sMoney.)Domain columns are unaffected because
PgTablesPluginregisters theMoneyoutput type for the domain codec (keyed by codec identity) andPgAttributesPluginresolves columns through that registration.Possible Solution
The domain-over-composite return should not be treated as a table/composite return; it should keep its own (domain) codec, which already has a registered output type. Excluding domain codecs from the composite branch so they fall through to the scalar/
elsebranch (which builds the resource withcodec: returnCodec) fixes it, the field is then exposed using the domain's already-registeredMoneytype, and no duplicateMoneyRawtype is created:if (!returnCodec.isAnonymous && + !returnCodec.domainOfCodec && (returnCodec.attributes || returnCodec.arrayOfCodec?.attributes)) {Verified against
graphile-build-pg@5.0.2: with this one-line change, a computed column returning a domain-over-composite is exposed using the same GraphQL type as the domain columns (Money), and the strayMoneyRawtype disappears.