I'm submitting a ...
PostGraphile version: 4.4.4
CREATE TABLE users (
id integer PRIMARY KEY
);
CREATE TYPE groups (
id integer PRIMARY KEY
);
CREATE TABLE slugs (
id varchar(255) PRIMARY KEY,
sluggable_id integer NOT NULL,
sluggable_type varchar(255) NOT NULL CHECK (sluggable_type::text = ANY (ARRAY['users'::text, 'groups'::text])::text[])
);
INSERT INTO users (id) VALUES (1);
INSERT INTO groups (id) VALUES (1);
INSERT INTO slugs (id, sluggable_id, sluggable_type) VALUES ('taz', 1, 'users');
INSERT INTO slugs (id, sluggable_id, sluggable_type) VALUES ('my-awesome-group', 1, 'groups');
When extending the schema as such:
import { makeExtendSchemaPlugin, gql } from 'graphile-utils'
makeExtendSchemaPlugin(({ pgSql: sql, graphql: { getNamedType }, $$nodeType }) => {
return {
typeDefs: gql`
union SluggableNode = User | Group
extend type Query {
nodeBySlugId(id: String!): SluggableNode @pgField
}
`,
resolvers: {
SluggableNode: {
__resolveType(obj) {
return obj[$$nodeType]
},
},
Query: {
async nodeBySlugId(root, { id }, { pgClient } , { graphile }) {
const {
rows: [slug],
} = await pgClient.query(
`select * from slugs where id = $1`,
[id]
)
if (!slug) {
throw new Error('Slug for Node not found')
}
console.log('SLUG IDENTIFIER')
console.log(slug.identifier_id)
const [
node,
] = await graphile.selectGraphQLResultFromTable(
sql.identifier(slug.sluggable_type),
(tableAlias, queryBuilder) => {
queryBuilder.where(
sql.fragment`${tableAlias}.id = ${sql.value(
slug.sluggable_id
)}`
)
}
)
if (node) {
Object.defineProperty(node, $$nodeType, {
enumerable: false,
configurable: false,
value: getNamedType(slug.sluggable_type === 'users' ? 'User' : 'Group'),
})
return node
}
return null
},
},
},
}
})
If I were to call:
{
nodeBySlugId(id: 'taz') {
... on User {
id
}
}
}
I receive:
{
"errors": [
{
"message": "Cannot return null for non-nullable field User.id.",
"locations": [
{
"line": 4,
"column": 7
}
],
"path": [
"nodeBySlugId",
"id"
]
}
],
"data": {
"nodeBySlugId": null
}
}
I should receive the ID for that referenced User:
{
"data": {
"nodeBySlugId": {
"id": 1
}
}
}
I'm attempting to have a table that houses all URL friendly strings such that I can have a /:slug URL that can then map to all of these entities.
From what I can see, PostGraphile is generating the correct query to get the node, however it's resulting in an empty object (i.e. {}) as the SELECT part of the query is empty (i.e. SELECT FROM users).
This leads me to believe that PostGraphile isn't "seeing" the fields that I've requested, likely due to the way that I'm performing this query?
Kindly note that I've extracted the above code from my application and thus it may not be 100% correct as I attempted to create a small reproducible example. However, it should provide a picture of the problem I'm facing and my attempt at finding a solution for it.
In an ideal world, I'd be able to write a Postgres function to handle this functionality. However, I'm unsure if a Postgres function can handle this type of polymorphic return in a way that would be useful to PostGraphile?
I attempted setting up https://github.com/hansololai/postgraphile-connection-filter-polymorphic as follows, without any luck either:
COMMENT ON COLUMN slugs.sluggable_type IS E'@isPolymorphic\n@polymorphicTo User\n@polymorphicTo Group';
Thank you in advance for assisting me with this question and kindly let me know if I can be of assistance with resolving it!
The problem you have is that selectGraphQLResultFromTable only works on types that have lookahead metadata generators and the union type you're using does not have any. As a rule of thumb you should only use selectGraphQLResultFromTable when the type of your field is (directly) a table or connection type that PostGraphile itself has generated.
We don't currently have an abstraction for what you're trying to achieve (support for unions in makeExtendSchemaPlugin was only added recently and hasn't been expanded to supporting query planning yet), so you have to copy/paste a far amount of boilerplate. Here's what the "row by unique constraint" resolver does:
This is basically what you need to do; in fact assuming you know what the type is going to be (which you seem to from the slugs table) you can basically use that code as-is but making sure you pick the relevant TableType and sqlFullTableName for the table the slug relates to.
So your resolver will be something along the lines of:
async resolve(_, args, resolveContext, resolveInfo) {
const { rows: [slug] } = await pgClient.query(
`select * from slugs where id = $1`,
[id]
);
if (!slug) {
throw new Error('Slug for Node not found');
}
/** The description of the table from our introspection */
const table = pgIntrospectionResultsByKind.class.find(tbl => tbl.namespaceName = MY_POSTGRAPHILE_SCHEMA && tbl.name === slug.identifier_type);
/** pg-sql2 representation of this table */
const sqlFullTableName = sql.identifier(table.namespaceName, table.name);
/** The GraphQL type associated with this table (has associated look-ahead metadata generators */
const TableType = getTypeByName(inflection.tableType(table));
const parsedResolveInfoFragment = parseResolveInfo(resolveInfo);
parsedResolveInfoFragment.args = args; // Allow overriding via makeWrapResolversPlugin
const resolveData = getDataFromParsedResolveInfoFragment(
parsedResolveInfoFragment,
TableType
);
const query = queryFromResolveData(
sqlFullTableName,
undefined,
resolveData,
{useAsterisk: false},
queryBuilder => {
queryBuilder.where(
sql.fragment`${queryBuilder.getTableAlias()}.id = ${sql.value(slug.identifier_id)}`
);
},
resolveContext,
resolveInfo.rootValue
);
const { rows: [row] } = await pgClient.query(sql.compile(query));
return row;
}
Note much of this is boilerplate, so if you need to use this in more places I recommend making a helper to do the common parts for you.
[semi-automated message] Thanks for your question; hopefully we're well on the way to helping you solve your issue. This doesn't currently seem to be a bug in the library so I'm going to close the issue, but please feel free to keep requesting help below and if it does turn out to be a bug we can definitely re-open it 👍
You can also ask for help in the #help-and-support channel in our Discord chat.
Thanks @benjie, I'll give that a shot 😄