crystal
crystal copied to clipboard
Composite domains don't resolve
Summary
Composite domains (what I'll call domains declared over composite types) are resolving to null.
When entered into psql, the Executed SQL Query output in PostGraphiQL outputs the expected composite domain values, however the fields remain as their sql field names and not their inflected GraphQL field name counterparts.
These issues go away if the domain is replaced with its underlying type.
Steps to reproduce
This isn't exactly a minimal reproduction, but I think it represents an exhaustive test case:
create type app_public._user_update_line_node_type as enum ('TEXT', 'MENTION');
create domain app_public.user_update_line_node_type as app_public._user_update_line_node_type not null;
create domain app_public.line_node_text as text not null;
create type app_public.base_user_update_content_line_node as (
line_node_type app_public.user_update_line_node_type,
line_node_text app_public.line_node_text
);
create domain app_public.user_update_content_line_node as app_public.base_user_update_content_line_node not null;
create domain app_public.user_update_content_line as app_public.user_update_content_line_node[] not null;
create type app_public.base_user_update_content as (
lines app_public.user_update_content_line[],
img_url text
);
create domain app_public.user_update_content as app_public.base_user_update_content not null
check ((
(
case
when ((value).lines is null) then 0
else 1
end +
case
when ((value).img_url is null) then 0
else 1
end
) > 0
));
create table app_public.posts (
post_id integer primary key generated by default as identity,
user_id integer not null,
content app_public.user_update_content not null,
thread_content app_public.user_update_content[],
created_at timestamp with time zone DEFAULT now() not null
);
Expected results
For composite domains to return the same results as their underlying types.
Actual results
Composite domains return null.
Additional context
PostGraphile 4.12.8 Node 16.13.1 MacOS 12.4
Possible Solution
Per @benjie's suggestion, I did a find and replace on affected domains to their underlying composite types. So far, so good!
Here's the gist (in line with the Steps To Reproduce DDL above):
graphileBuildOptions: {
pgAugmentIntrospectionResults(results: any) {
function findAndReplaceType(
original: { schema: string; field: string },
replacement: { schema: string; field: string }
) {
// get original typeId
const originalType = results.type.find(
(i: any) =>
i.namespaceName === original.schema && i.name === original.field
);
if (originalType === undefined) {
throw new Error(`originalType not found`);
}
// get replacement typeId
const replacementType = results.type.find(
(i: any) =>
i.namespaceName === replacement.schema && i.name === replacement.field
);
if (replacementType === undefined) {
throw new Error(`replacementType not found`);
}
// find and replace in types
results.type.forEach((i: any, idx: number, arr: any[]) => {
if (i.arrayItemTypeId === originalType.id) {
arr[idx].arrayItemTypeId = replacementType.id;
}
});
// find and replace in attributes
results.attribute.forEach((i: any, idx: number, arr: any[]) => {
if (i.typeId === originalType.id) {
arr[idx].typeId = replacementType.id;
}
});
}
findAndReplaceType(
{ schema: "app_public", field: "user_update_content" },
{ schema: "app_public", field: "base_user_update_content" }
);
findAndReplaceType(
{ schema: "app_public", field: "user_update_content_line" },
{ schema: "app_public", field: "_user_update_content_line_node" }
);
findAndReplaceType(
{ schema: "app_public", field: "user_update_content_line_node" },
{ schema: "app_public", field: "base_user_update_content_line_node" }
);
return results;
},
},
Etc
Composite domains are generally nice because they allow a new class of constraints (like a composite type of two fields that doesn't permit two nulls).
In addition, they also offer a way around one of PostGraphile's trouble spots: multidimensional arrays.
Referencing the example in Steps To Reproduce DDL, app_public.user_update_content_line[] inflects to [[UserUpdateContentLineNode]] because another array (app_public.user_update_content_line_node[]) underlies app_public.user_update_content_line.