Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/strict-apes-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"graphile-build-pg": minor
---

improve smart tags for polymorphic types

- fix @returnType for functions returning setof (for lists and connections)
- support @returnType for foreign key constraints
- add @foreignReturnType for the backward relation of a foreign key constraint
- add @applyToType for computed columns, so a computed column defined on a
polymorphic PostgreSQL table can be exposed only on a specific concrete
GraphQL type.
6 changes: 6 additions & 0 deletions graphile-build/graphile-build-pg/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ declare global {
nodeIdCodec: string;
/** For functions returning polymorphic type, which type to choose? */
returnType: string;
/** For computed attribute functions, which GraphQL type(s) should receive this field? */
applyToType: string | string[];

/** For enum tables; we shouldn't expose these through GraphQL */
enum: string | true;
Expand All @@ -80,6 +82,10 @@ declare global {
behavior: string | string[];
deprecated: string | string[];
notNull: true;
/** For forward relations against polymorphic types, which type to choose? */
returnType: string;
/** For backward relations against polymorphic types, which type to choose? */
foreignReturnType: string;
}

interface PgCodecRefTags extends PgSmartTagsDict {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ const $$rootQuery = Symbol("PgCustomTypeFieldPluginRootQuerySources");
const $$rootMutation = Symbol("PgCustomTypeFieldPluginRootMutationSources");
const $$computed = Symbol("PgCustomTypeFieldPluginComputedSources");

function tagToStrings(
tag: undefined | null | boolean | string | (string | boolean)[],
): string[] {
if (!tag || (Array.isArray(tag) && tag.length === 0)) {
return [];
}
return (Array.isArray(tag) ? tag : [tag]).flatMap((entry) =>
typeof entry === "string" ? [entry] : [],
);
}

declare global {
namespace GraphileConfig {
interface Plugins {
Expand Down Expand Up @@ -1158,6 +1169,18 @@ function modFields(
return procSources.reduce(
(memo, resource) =>
build.recoverable(memo, () => {
const applyToTypes = tagToStrings(
resource.extensions?.tags?.applyToType,
);
const shouldSkipForApplyToType =
isRootQuery || isRootMutation
? false
: applyToTypes.length > 0
? !applyToTypes.includes(SelfName)
: false;
if (shouldSkipForApplyToType) {
return memo;
}
// "Computed attributes" skip a parameter
const offset = isRootMutation || isRootQuery ? 0 : 1;

Expand Down Expand Up @@ -1332,6 +1355,10 @@ function modFields(
});

const namedType = build.graphql.getNamedType(type!);
const preferredConnectionTypeName =
resource.extensions?.tags?.returnType && namedType
? inflection.connectionType(namedType.name)
: null;
const connectionTypeName = shouldUseCustomConnection(resource)
? resource.codec.attributes
? inflection.recordFunctionConnectionType({
Expand All @@ -1340,11 +1367,13 @@ function modFields(
: inflection.scalarFunctionConnectionType({
resource,
})
: resource.codec.attributes
? inflection.tableConnectionType(resource.codec)
: namedType
? inflection.connectionType(namedType.name)
: null;
: preferredConnectionTypeName
? preferredConnectionTypeName
: resource.codec.attributes
? inflection.tableConnectionType(resource.codec)
: namedType
? inflection.connectionType(namedType.name)
: null;

const ConnectionType = connectionTypeName
? build.getOutputTypeByName(connectionTypeName)
Expand All @@ -1368,9 +1397,10 @@ function modFields(
{
description:
resource.description ??
`Reads and enables pagination through a set of \`${inflection.tableType(
resource.codec,
)}\`.`,
`Reads and enables pagination through a set of \`${
namedType?.name ??
inflection.tableType(resource.codec)
}\`.`,
type: build.nullableIf(
isRootQuery ?? false,
ConnectionType,
Expand Down
17 changes: 14 additions & 3 deletions graphile-build/graphile-build-pg/src/plugins/PgRelationsPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ declare global {
foreignSimpleFieldName?: string;
/** The (generally plural) backward relation name, also used as a fallback from foreignSimpleFieldName, foreignSingleFieldName */
foreignFieldName?: string;
/** The GraphQL return type for the forward relation */
returnType?: string;
/** The GraphQL return type for the backward relation */
foreignReturnType?: string;
}
}

Expand Down Expand Up @@ -933,10 +937,17 @@ function addRelations(
}

const isUnique = relation.isUnique ?? false;
const preferredTypeName = tagToString(
relation.isReferencee
? relation.extensions?.tags?.foreignReturnType
: relation.extensions?.tags?.returnType,
);
const otherCodec = remoteResource.codec;
const typeName = build.inflection.tableType(otherCodec);
const connectionTypeName =
build.inflection.tableConnectionType(otherCodec);
const typeName =
preferredTypeName ?? build.inflection.tableType(otherCodec);
const connectionTypeName = preferredTypeName
? build.inflection.connectionType(preferredTypeName)
: build.inflection.tableConnectionType(otherCodec);

const deprecationReason =
tagToString(relation.extensions?.tags?.deprecated) ??
Expand Down
5 changes: 5 additions & 0 deletions postgraphile/postgraphile/__tests__/kitchen-sink-data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,11 @@ update polymorphic.single_table_items
where single_table_items.id = cte.id
and cte.id != cte.rti;

insert into polymorphic.foreign_key_return_type_tests
(id, topic_id) values
(4, 1);
alter sequence polymorphic.foreign_key_return_type_tests_id_seq restart with 9999;

insert into polymorphic.single_table_item_relations
(id, parent_id, child_id) values
(1, 1, 3),
Expand Down
48 changes: 48 additions & 0 deletions postgraphile/postgraphile/__tests__/kitchen-sink-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1440,15 +1440,63 @@ create function polymorphic.single_table_items_meaning_of_life(sti polymorphic.s
select 42;
$$ language sql stable;

create function polymorphic.single_table_items_post_only(
sti polymorphic.single_table_items
)
returns text as $$
select 'post only: ' || sti.title;
$$ language sql stable;
comment on function polymorphic.single_table_items_post_only(
polymorphic.single_table_items
) is '@applyToType SingleTablePost';

create function polymorphic.single_table_items_post_and_divider_only(
sti polymorphic.single_table_items
)
returns text as $$
select 'post or divider only: ' || sti.title;
$$ language sql stable;
comment on function polymorphic.single_table_items_post_and_divider_only(
polymorphic.single_table_items
) is $$
@applyToType SingleTablePost
@applyToType SingleTableDivider
$$;

create function polymorphic.all_single_tables ()
returns setof polymorphic.single_table_items as $$
select * from polymorphic.single_table_items
$$ language sql stable;

create function polymorphic.single_table_items_topics(
sti polymorphic.single_table_items
)
returns setof polymorphic.single_table_items as $$
select *
from polymorphic.single_table_items
where type = 'TOPIC'
order by id asc;
$$ language sql stable;
comment on function polymorphic.single_table_items_topics(
polymorphic.single_table_items
) is '@returnType SingleTableTopic';

comment on constraint single_table_items_root_topic_fkey on polymorphic.single_table_items is $$
@behavior -*
$$;

create table polymorphic.foreign_key_return_type_tests (
id serial primary key,
topic_id int constraint foreign_key_return_type_tests_topic_fkey references polymorphic.single_table_items on delete cascade
);
comment on table polymorphic.foreign_key_return_type_tests is E'@behavior -insert -update -delete -filter -filterBy -order -orderBy';
comment on constraint foreign_key_return_type_tests_topic_fkey on polymorphic.foreign_key_return_type_tests is $$
@fieldName topicByReturnType
@returnType SingleTableTopic
@foreignFieldName childTopicsByReturnType
@foreignReturnType SingleTablePost
$$;

create table polymorphic.single_table_item_relations (
id serial primary key,
parent_id int not null references polymorphic.single_table_items on delete cascade,
Expand Down
Loading