Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Table inheritance / interfaces #387

Closed
EyMaddis opened this issue Mar 7, 2017 · 59 comments
Closed

Table inheritance / interfaces #387

EyMaddis opened this issue Mar 7, 2017 · 59 comments

Comments

@EyMaddis
Copy link
Contributor

EyMaddis commented Mar 7, 2017

Is there a way to model interfaces using PostgraphQl?

I guess that this would require to map any metadata to a table which could be a problem.


See potential solution: #387 (comment)

@calebmer
Copy link
Collaborator

calebmer commented Mar 7, 2017

Not currently, but we could do something with Postgres table inheritance. What is your use case?

@EyMaddis
Copy link
Contributor Author

EyMaddis commented Mar 7, 2017

I think the search example from this post is quite good:
https://medium.com/the-graphqlhub/graphql-tour-interfaces-and-unions-7dd5be35de0d#.c4hzu9enw

As I understand it right now, I would need to write a postgres function which returns either a specific type of a table or declare a custom type which only contains the properties for that specific result set.
The latter of which would lead to a lot of unnecessary types and does not allow the beautiful ... on MyType {... syntax in GraphQl, or am I wrong?

Table inheritance might be an option, but in some cases (like the search example) this would lead to tables which are created, but do not contain any data and would even generate GraphQL queries/mutations.

Maybe the generation could be avoided by placing the "abstract" tables into a schema that is not accessible to PostGraphQl

@calebmer
Copy link
Collaborator

calebmer commented Mar 7, 2017

The tables you inherit from you’d want to generate queries, but you wouldn’t want mutations. So just GRANT SELECT, but not inserts, updates, or deletes 😉

If we were to do this we would need to find a way to represent a union or an interface in Postgres. I’m not sure how that would be done.

The way I generally design my schema is I have a single table which has a type enum and then has some fields which are specific to that type. So if an option for type on a search table were person then I may also have a column called person_id.

@benjie
Copy link
Member

benjie commented Mar 8, 2017

Maybe something like:

create type postgraphql_union__search_result as (
  "user" "user",
  post post,
  comment comment,
  book book,
  topic topic,
  ...
); -- Exactly one of these should be not-null at a time

create function search_results() returns setof postgraphql_union__search_result ...

?

@EyMaddis
Copy link
Contributor Author

EyMaddis commented Mar 8, 2017

I think with that approach the make clear which types the union combines.
Suppose you have two tables: Person and Organization a union should look something like this:
postgraphql_union__Person__Organization.

@calebmer
Copy link
Collaborator

calebmer commented Mar 8, 2017

I think @benjie’s proposal would be a fine thing to explore if anyone is interested in building out the implementation. That was what I imagined unions would probably have to look like.

@benjie
Copy link
Member

benjie commented Mar 8, 2017

@EyMaddis The reason I didn't do that is because I wanted you to be able to extend the union later with the minimum of effort.

@EyMaddis
Copy link
Contributor Author

EyMaddis commented Mar 8, 2017

But how can postgraphql know which type the union combines?

@calebmer
Copy link
Collaborator

calebmer commented Mar 8, 2017

@EyMaddis we can introspect the attributes on the type 😊

Every attribute represents a different type in the union. PostGraphQL would then only resolve the first non-null attribute it finds expecting all of the other attributes to be null.

@EyMaddis
Copy link
Contributor Author

EyMaddis commented Mar 8, 2017

@calebmer ah, alright, got it. I did not pay close attention to the types of the attributes.

I think I like that, but it would be kind of awkward when writing the functions, wouldn't it?
Probably something like a switch-case where the type defines which attribute to put the data to.

something similar could be applied to interfaces, but postgraphql could let tables "inherit" an interface if the properties defined in the type matches the columns.
Not saying that this is the best idea, just a possibility.

@EyMaddis
Copy link
Contributor Author

EyMaddis commented Apr 7, 2017

I can currently somehow achieve what I want through inheritance into another schema.
There are two schemas, one for internal functions and my interface tables and my public schema that PGQL has access to.

I created a timestamped interface that has info like created_date and a lot of tables inherit from that interface (there are also triggers for these tables). Due to the fact that the timestamped table is in my internal schema, PGQL will not generate the default mutations/queries, but I inherit all my tables.
The only missing part is that PGQL checks for inheritance and recognizes timestamped as an interface.

Edit: by the way, multi-inheritance is possible

@calebmer
Copy link
Collaborator

Could you put together a quick demo so we can see what this looks like in practice? This is an interesting idea using table inheritance with an empty table.

@EyMaddis
Copy link
Contributor Author

@calebmer what do you require exactly? Some SQL commands to set the tables as described above?

CREATE SCHEMA interfaces; -- no access for PostGraphQL
CREATE SCHEMA postgraphql; -- access granted to PostGraphQL


CREATE TABLE interfaces.timestamped ( 
	-- no id, because it would increase the serial with every document created by a child
    "created_date" date DEFAULT now()
);

CREATE TABLE interfaces.other_interface ( 
    "something_inherited" text
);

CREATE TABLE postgraphql.something(
    "id" serial,
    "something_specific" text,
    PRIMARY KEY ("id")
) INHERITS (interfaces.timestamped, interfaces.other_interface);

@justinmakaila
Copy link

Do we have any updates on this, or if anyone's working on it? We're thinking about picking this up, interested in what people have tried or run into.

@jide
Copy link

jide commented Jul 25, 2018

I would love to see this feature, so I'm willing to try to implement this in a plugin.
It's a bit scary though, the plugins api is not easy to grasp with.
I would love some baby steps directions to start this right.

The first thing I want to do is adding a hard coded union type. I guess I have to use newWithHooks(), but looking at other plugins does not help.

@jide
Copy link

jide commented Jul 25, 2018

...or is this not even feasible in a plugin because there is no GraphQLUnionType implemented here ? https://github.com/graphile/graphile-build/blob/master/packages/graphile-build/src/SchemaBuilder.js#L159

Yes i'm kind of lost :)

@benjie
Copy link
Member

benjie commented Jul 25, 2018

I have plans to add this to core, but they're not far along. My plan is:

For every table foo that is inherited by another table (create table bar (...) inherits (foo);, create an interface type named after the table FooInterface. Then the table type would implement this interface type Foo implements FooInterface and all children below there implement the interface also type Bar implements FooInterface. Effectively you'd need a plugin that looks through the classes and defines these interfaces. Then you need a plugin that hooks every time a table type is defined and adds an interfaces thunk (ref) returning the interface. That should be enough to get the schema to introspect as supporting the interfaces.

However the complexity comes in the look-ahead logic. Currently it tries to resolve concretely which type will be returned ahead of time so it knows which SQL fields to request, this is going to need some thought. I've not got this far yet. I'm sure there's many other complexities I've not thought about yet as well.

Since I'm not really sure what shape this solution should take yet, I don't think I can advise you how best to implement it.

As for newWithHook it's really just an indirect way of calling the GraphQL.js constructors, documented here: https://graphql.org/graphql-js/type/

e.g. in graphql.js you'd do

const MyInterfaceType = new GraphQLInterfaceType({
  name: "MyInterface",
  fields: () => ({id: {type: GraphQLString } }),
  description: "My interface type",
};

In graphile you'd do:

const MyInterfaceType = newWithHooks(GraphQLInterfaceType, {
  name: "MyInterface",
  fields: () => ({id: {type: GraphQLString } }),
  description: "My interface type",
}, {
  // Optionally add some things here so other plugins know what this type
  // is all about and whether or not they should extend it.
});

The hooks themselves just take the object spec:

                                                           {
  name: "MyInterface",
  fields: () => ({id: {type: GraphQLString } }),
  description: "My interface type",
}

and return a new version of the object spec:

{
  ...oldObjectSpec,
  description: "My much better description",
}

@benjie
Copy link
Member

benjie commented Jul 25, 2018

not even feasible in a plugin because there is no GraphQLUnionType implemented here

That means you can't use newWithHooks on GraphQLUnionType; but you can build a raw one from graphql.js and use that. However, the issue here is you can't register the fields with the look-ahead mechanic.

A PR adding GraphQLUnionType support would be welcome; but it'd have to come with a lot of tests!

@jide
Copy link

jide commented Jul 25, 2018

Thank you for the detailed response @benjie.

To be honest, this seems like a task that needs a deep knowledge of the API, and you seem to have thought this through a lot, so I guess it's hopeless for me to implement this the right way. Maybe I'll try to create simpler plugins first.

So... let us know when there is progress with this, if there is :) It's a feature every graphql->db lacks from what I've seen, and whatever lib i try I'm stuck with this problem in my use case.

@jide
Copy link

jide commented Jul 25, 2018

Did not see your last comment. That could be a reasonable PR, but I'm not even sure. Maybe I'll give it a shot after having read the code a little more deeply... maybe not... I'll let you know how I feel about this.

@benjie
Copy link
Member

benjie commented Jul 25, 2018

Yeah; it's definitely a non-trivial problem. I'll update this thread when progress is made, but this is not currently high on my TODO list.

@benjie benjie changed the title How to model interfaces? Table inheritance / interfaces Aug 15, 2018
@benjie benjie added this to the 5.0 milestone Aug 16, 2018
ThomasWeiser added a commit to ThomasWeiser/mediatum-view that referenced this issue Oct 10, 2018
This simplifies frontend's Graphqelm code.

In theory, we could also use a GraphQL interface
for the two fields of FolderCount.
But Postgraphile doesn't support GraphQL interfaces (yet):
graphile/crystal#387
@ab-pm
Copy link
Contributor

ab-pm commented Sep 17, 2019

Hi @mathroc that seems like a nice idea. With CREATE TYPE person and CREATE TYPE emailable (instead of CREATE TABLE), I believe this is quite close to what @benjie was thinking of.
I really like the declarativeness of the interface creation. Now the only thing that is missing is Postgres supporting CREATE TABLE (…) INHERITS (emailable, person) (where it currently complains "emailable is a composite type")…

While the functions bring great freedom into how a table implements an interface, the big downside is that there are now two ways to access a name field on a row:

SELECT json_build_object('name', "user".name) FROM "user";
SELECT json_build_object('name', person_from("user").name) FROM "user";

In PostGraphile, these probably would be a collision of two fields with the same name. This might require you to @omit the table fields and have virtual constraints on the interface type. I'd rather prefer PostGraphile to detect matching columns between the interface type and the table itself (given a COMMENT ON TABLE "user" IS '@implements person'), and require such a conversion function only iff they do not match.

@mathroc
Copy link
Contributor

mathroc commented Sep 17, 2019

if I recall correctly, the issue with create type is that it makes all fields nullable

@benjie
Copy link
Member

benjie commented Sep 17, 2019

We can circumvent that either using domains or smart comments. I believe @nonNull already works.

We should also keep in mind user-defined interfaces, or interfaces where the columns may not match perfectly but are "fudged" via smart comments or plugins.

@ab-pm
Copy link
Contributor

ab-pm commented Sep 18, 2019

Just for reference, here's how JoinMonster does (did - the library is basically dead) support unions & interfaces: https://join-monster.readthedocs.io/en/latest/unions/

@benjie
Copy link
Member

benjie commented Sep 19, 2019

Interesting, that means union types cannot share the names of columns with different types. E.g. if you had one table with id uuid primary key and one with id serial primary key you couldn't do a union of them. Quite a limited solution.

@ab-pm
Copy link
Contributor

ab-pm commented Nov 20, 2019

From discord:

@benjie: What I’m planning to add is polymorphism where a single table can represent multiple types. E.g. animal/cat/dog/fish - some share attributes, some unshared.

I thought of that idea briefly but dismissed it because I didn't see a good way to create multiple types with overlapping columns declaratively. Maybe declare them as composite types elsewhere and have a @stores X, Y, Z annotation on the table?
If you could come up with an example that I can run tests against that would be dope.

@benjie
Copy link
Member

benjie commented Nov 23, 2019

Think of the blocks in a Medium-style post:

  • Paragraph (text)
  • Title (text)
  • Subtitle (text)
  • Image (url, text (caption), alt_text)
  • Link (url, text)
  • Video (provider, identifier, text (caption))

Perhaps:

type Block {
  id: Int!
  text: String
  author: User
  lastEdited: Datetime
}
type Image implements Block {
  id: Int!
  text: String
  url: String
  altText: String
}
type Title implements Block ...

From:

create table blocks (
  id serial primary key,
  type text default 'Paragraph',
  text text,
  author_id int references users,
  last_edited timestamptz not null default now(),
  url text,
  alt_text text,
  provider text,
  identifier text
);

or maybe a central block table with 1-to-1 relationships for the extra properties.

create table block_images (
  block_id int primary key references block,
  url text,
  alt_text text  
};

Maybe the relationship would be the other way so you can use an XOR check constraint to ensure there's exactly one related type - this may mean there's no nead for type column: if block_image_id is not null then it's an Image, etc.

I've not thought sufficiently deeply about this yet.

@benjie
Copy link
Member

benjie commented Nov 23, 2019

If you have a concrete use case you should share it. We shouldn't build things for the sake of having them, we should build them because they're solving real problems people have.

We should also not be too prescriptive about people's database models - if we can make a particular solution work with multiple different layouts of database then that's better. Using patterns that are in the wild already is better because then it can benefit more people out of the box (and is not controversial).

@EyMaddis
Copy link
Contributor Author

EyMaddis commented Nov 23, 2019

We implement a content management system for movies., series and such. A movie, episode, series etc pretty much share all the fields except for some columns.
Right now we actually use Postgres inheritance but it is a pain because queries do not use FROM ONLY table and foreign keys must be set on all the tables.

Also relevant for a global search function that can query multiple types, as suggested at the start of this topic.

@ab-pm
Copy link
Contributor

ab-pm commented Nov 25, 2019

Hello!
My real-world use cases involve usergroups that can contain both individual users and other usergroups (looking for a union here), and something that's exactly similar to this structure (where I want an interface for the treenode type) but I can't share in further detail.

I've already started hacking, I guess I should have a PR to look at (though probably nowhere near readiness for merge) this week. As @benjie said, Postgraphile will need to be able to deal with any database layouts that people might have used. My current plan is to support the following patterns:

  • a selectable common table ("entity") with union/interface rows
    • "subclass" inheritance: each union member/interface implementing type subtable has a primary key that foreign-key-references the primary key of the union/interface table, i.e. sharing ids between all entities.
    • (any) unique foreign key references from the subtable to the common table, and some kind of "type resolver" in the common table to select the subtable
    • (nullable) unique foreign key references from the common table to the subtables, with one column per subtable and a XOR check constraint.
      Interfaces really are just treated the same as unions here regarding the 1:1 relation between common and subtable, except they have some shared columns in the common table (and a different smart tag…)
  • a custom composite type declaring an interface, with each table having its own columns storing its own values. Possibly with a customisable mapping from each table to the composite type.
    This is very similar to table inheritance, where each inherited table just "copies" the column definitions. Here, most queries would work out of the box, it just needs that implements Interface declaration on the type.
    • Shared foreign key constraints (forward relations in the interface fields) would have an option to generate a single backward relation to the interface type, instead of multiple backward relations to each implementing type.
  • unions for backward relations: multiple referencing table generating the backward fields with the same name become a single backward field with a union type on the foreign table
  • unions for forward relations: multiple foreign-key columns with a XOR check constraint become only a single forward field with a union type

Thanks for the examples you've come up with, I'll try to implement them in the test cases for demonstration.

@ab-pm
Copy link
Contributor

ab-pm commented Nov 26, 2019

Having thought more about the queries for shared fields, I've come to the conclusion that it would be unnecessarily complicated trying to merge everything into a single object on the Postgres level already. There's no reason to do that, we're still using resolve() methods anyway (to resolve safe aliases) and they give us the flexibility to work with nested objects just as easily. So it'll be more like

SELECT json_build_object(
  'foo_x', foo.x,
  'foo_bar', (SELECT json_build_object(
    'shared', bar.shared,
    '__concrete', (CASE bar_resolveType(bar)
      WHEN 'user'::regclass::oid THEN (
        SELECT json_build_object('__typename', 'User'::text, 'username', users.username)
        FROM users WHERE users.id = bar.id)
      WHEN 'posts'::regclass::oid THEN (
        SELECT json_build_object('__typename', 'Blog'::text, 'title', posts.title)
        FROM posts WHERE posts.id = bar.id)
      ELSE pg_raise('no concrete type found')
    END CASE)
  )
  FROM bar
  WHERE bar.id = foo.bar_id)
)
FROM foo

Now I only need to adopt parseResolveInfo/simplifyParsedResolveInfoFragmentWithoutType to be able to support this structure :-)

@ab-pm
Copy link
Contributor

ab-pm commented Nov 27, 2019

Ah, found a fun problem: assuming we have concrete object types X, Y and Z which implement the interface Bar with a shared field (stored in a common referenced table), a GraphQL query can be quite flexible and make subfield queries type-dependent:

query {
  allBars {
    __typename
    shared { a }
    ... on X {
      shared { b d }
    }
    ... on Y {
      shared { c d }
    }
  }
}

It could lead to a result such as

{ data: [
  { __typename: "X", shared: { a: 1, b: 2, d: 4 } }, // no c
  { __typename: "Y", shared: { a: 1, c: 3, d: 4 } }, // no b
  { __typename: "Z", shared: { a: 1 } }, // no b or c
] }

The fields a through d could of course stem from complicated subqueries not be simple scalars. What would the SQL query look like?

  • avoid repetition and just do overfetching: have a single select for the shared field that gets all of a, b, c and d on each shared object, no matter what concrete type it has
  • avoid repetition, and propagate the type conditions down the tree: have a single select for the shared field but then wrap the queries for b, c and d in CASE expressions depending on the parent's concrete type
  • repeat the queries, just like in the graphql code itself: do separate selects for the shared field on all objects, for the shared field on all objects satisfying X (which might be an interface not a concrete type!), and for the shared field on all objects satisfying Y, then do a recursive merge of the results (either in SQL using jsonb, or in JS) - it will be a hassle for list fields though.
  • repeat the queries for every possible concrete type/combination of type conditions, i.e. statically evaluating the possible merges beforehand - this could lead to combinatorial explosion though

I tend to favour the first approach (overfetching) because it's the simplest and requires the fewest code changes, then later implement the second approach (condition propagation) if anyone deems it necessary.

@micimize
Copy link

micimize commented Dec 8, 2019

If you have a concrete use case you should share it

Every entity in my system inherits from a base table that is more or less:

create table if not exists internal.base (
  entity_id       uuid default uuid_generate_v1mc(),
  valid_from      timestamp with time zone not null default now(),
  valid_until     timestamp with time zone not null default 'infinity',

  -- this is not inherited, and has to be copied
  primary key (entity_id, valid_until)
  unique (entity_id, valid_from)
);

This is mostly for convenience - I would eventually like to move away from inheritance at the db level. However, dealing with the universally implemented interface at the postgraphile level would be a win for me even without table inheritance.

I think an ideal extension that satisfies my use case would just take an interface definition, along with a matcher function by which to apply that definition. Essentially a generalized version of the approach used in determining shouldHaveNodeId. I can't tell if table constraints can be gotten in user land though. Maybe through context?

Imagined usage would be something like:

appendPlugins: [implementInterface({
  interface: interfaceDefinition,
  when: (type, table) => 
    hasMyInterfaceFields(type) &&
    hasMyInterfaceConstraints(table),
})]

@ab-pm
Copy link
Contributor

ab-pm commented Dec 9, 2019

@micimize Ah, cool, thanks for the ideas! I guess this actually should be quite possible already by hooking onto GraphQLObjectType:interfaces. Indeed, the table constraints are available through context.scope.pgIntrospection.constraints to all plugins - there's no distinction made between builtin and "userland" ones.
The only thing missing here is creating the interface type itself, and accessing it from inside the GraphQLObjectType:interfaces hook. The first one could be achieved by a makeExtendSchemaPlugin or maybe even raw new GraphQLInterfaceType() instantiation. (Creating it from postgres' internal.base table takes a bit more effort, but that part of the code I have working already). hasMyInterfaceFields will be the most complicated part here, as inspecting the fields and their types might mess with lazyness of the field hooks (cf. #575).
But if your tables do already have all these columns themselves, lookahead features will not be affected by the interface declaration, the object types already work for you, so this will work out of the box.

@ab-pm
Copy link
Contributor

ab-pm commented Dec 18, 2019

I got my first (specific) union type with complete lookahead working, see #1210 (comment)! So it definitely seems possible, and I hope I can soon make the PR to cut the rough edges in the graphile core that would make this even easier. Then the step to a generic support for generating these types for arbitrary annotated tables won't be large any more…

benjie added a commit that referenced this issue Jan 27, 2020
Removes the new inflectors added by 4.3.0 (which was never upgraded to `@latest`, so this is not a breaking change) and instead puts them all through a single function called `builtin`.

Also upgrades `makeAddInflectorsPlugin` so that it accepts a function, allowing replacement inflectors to reference the previous inflectors.
@RedShift1
Copy link

RedShift1 commented Feb 29, 2020

Note that table inheritance is listed in the PostgreSQL wiki as a "Don't do this".

@ulo
Copy link

ulo commented Mar 11, 2020

Even without table inheritance, the possibility to return GraphQL union types in a resolver would be really nice to have!
I hope a generic solution based on the solution by @ab-pm will be available soon...

@klemens-morgenstern
Copy link

I just spent a bit of time trying to figure this out, but I am sort of stuck on trying to figure out the graphile code base. I would solve it the following, and would like to know if you think it's doable that way:

We want a schema like this

interface foobar 
{ 
    name: String
    lowerCase: String
}

type foo implements foobar 
{
    name: String!
    id: Int! 
}

type bar implements foobar 
{
    name: String!
    id: Int! 
}

Pure Interface Types

To implement inheritances us the @implements & @interface smart comments.

What we want is a schema that looks like this:

interface foobar { name: String }

type foo implements foobar 
{
    name: String!
    id: Int! 
}

type bar implements foobar 
{
    name: String!
    id: Int! 
}

If we just want the interface type, i.e. no query , then using a type like this should work:

Here is an example of using a types, i.e. one that cannot be queried in Query.

create type p.foobar as(name text);
comment on type p.foobar is '@interface';

create table p.foo (id serial primary key, name text not null);
comment on table p.foo is '@implements foobar';

create table p.bar (id serial primary key, name text not null);
comment on table p.bar is '@implements foobar';

Another version would be to have a table that can represent two different types, and I want to represent them like this in the schema;

create table p.foobar (id serial primary key, name text not null, is_foo boolean);

comment on table p.foobar is '@interface';
comment on column p.foobar.is_foo is '@omit'; 

create view p.foo as select id, name from p.foobar where is_foo;
comment on view p.foo is '@implements foobar';

create view p.bar as select id, name from p.foobar where not is_foo;
comment on view p.bar is '@implements foobar';

And as third option you could put two different tables in a common view, like so

create table p.foo (id serial primary key, name text not null);

create table p.bar (id serial primary key, name text not null);

--tableoid is required to determine the type
create view p.foobar as
    select id, name, tableoid from p.foo
union all
    select id, name, tableoid from p.bar;

comment on view p.foobar is '@interface';

comment on table p.foo is '@implements foobar';
comment on table p.bar is '@implements foobar';

Any thoughts on the design idea?

@mathroc
Copy link
Contributor

mathroc commented May 28, 2020

@klemens-morgenstern it’s nice to see alternatives, I like the third option. The second one does not allow implementing multiple interfaces, so I think that would be a problem.

The first one could work but I like the third one better because it allows transforming data from the source table to match the interface properties. On your example, for Postgraphile to be able to generate the correct GraphQL interface, you would remove the id column from the p.foobar view, right ?

I had a different suggestion that would like this in your example:

create table p.foo (id serial primary key, name text not null);
create table p.bar (id serial primary key, name text not null);
create type p.foobar as(name text);
comment on type p.foobar is '@interface';

create function foobar_from(foo) returns p.foobar as $$
  select row($1.name)::p.foobar
$$ language sql;

create function foobar_from(bar) returns p.foobar as $$
  select row($1.name)::p.foobar
$$ language sql;

from a maintenance point of view I like having multiple function instead of a global view but I don’t know if there’s performance implication from either implementation

@klemens-morgenstern
Copy link

I would like to have both 2 & 3. If you break it down to something that is query-able, i.e. a view, matieralized view, table, and has a tableoid, you would have the most flexibility. You could also use table inheritance, should you so desire.

In my example the id would not be removed, but graphile could use id & tableoid to generated a unique nodeId.

I also tried to implement a prototype myself, but already failed on changing a type to an interface with hook. This whole engine is really complicated.

@benjie
Copy link
Member

benjie commented May 29, 2020

I suggest you familiarise yourself with GraphQL.js before diving into the Graphile Engine codebase. We don’t have a way of changing the constructor of a type (GraphQLObjectType, GraphQLInterfaceType, etc), the engine is not designed to do that.

@heri16
Copy link

heri16 commented Sep 24, 2020

I got my first (specific) union type with complete lookahead working, see #1210 (comment)! So it definitely seems possible, and I hope I can soon make the PR to cut the rough edges in the graphile core that would make this even easier. Then the step to a generic support for generating these types for arbitrary annotated tables won't be large any more…

Any updates on this PR?

@benjie benjie added the 🔁 revisit-in-v5 Not happy with this behaviour, but changing it is a breaking change. label Sep 23, 2022
@benjie
Copy link
Member

benjie commented Jul 21, 2023

Polymorphism is now supported in PostGraphile V5 (and has been for a while, and is receiving production usage by some users); read more here: https://dev.to/graphile/intro-to-postgraphile-v5-part-5-polymorphism-40b4

@benjie benjie added 🔮 fixed-in-v5 and removed 🔁 revisit-in-v5 Not happy with this behaviour, but changing it is a breaking change. labels Sep 29, 2023
@benjie benjie closed this as completed Sep 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests