feature: Polymorphic Relations
Feature Request: Polymorphic Relations
The ability to create polymorphic one-to-one, one-to-many, and many-to-many relationships in Hasura. This would necessitate Hasura generating appropriate union or interface graphql types for the polymorphic association (or some way for the user to create these types).
- an example of a polymorphic association is a
contact listwhich contains bothpersonandorganizationsubscribers.
Polymorphic associations have a number of advantages over regular associations which cannot be currently achieved:
- The ability to cascade deletion of a polymorphic association (or restrict updates, etc).
- The ability to provide the appropriate domain model in your graphql schema (using
unionorinterfacetypes).
Some examples which could benefit from polymorphic associations:
- A database has Person, Organization, and Event records which are each associated with an Address record. The address record has a standard shape, so it might make sense to model the data with
people,organizations,events, andaddressestables.- Currently, you might add an
address_idcolumn to thepeople,organizations, andeventstables which acts as a foreign key to an address record. However, this does not allow you to automatically delete the address associated with a Person, Organization, or Event.- To automatically delete the address associated with a person when the person is deleted, you can manually create a postgres trigger on the
peopletable. It would be nice if Hasura supplied an option to create this trigger for you.
- To automatically delete the address associated with a person when the person is deleted, you can manually create a postgres trigger on the
- Alternatively, you could add
person_addresses,organization_addresses, andevent_addressestables instead of a singleaddressestable. However this denormalizes your database schema and unnecessarily complicates the generated graphql schema.
- Currently, you might add an
- A database has ContactList records which have a
many-to-manyassociation with both Person and Organization records.- Currently, this must be modeled using
organization_subscriptionandperson_subscriptionjoin tables. If a graphql client wants to be able to pull the first 10 subscribers of a contact list in alphabetical order, you must create a custom sql view. Problematically, the graphql associated with this custom SQL view cannot return an interface/union type oforganizationandpersonrecords. This can be worked around by returning an artificial subscription object withpersonandorganizationproperties which would contain either a person or organization. However jumping through these hoops is not ideal.
- Currently, this must be modeled using
Possible implementations
As an example, Rails' Active Record supports polymorphic associations by utilizing an additional type column for associations. The type column contains the name of the table, while the standard foreign key column contains the id of the table row.
- In our first example above, the
addressestable might model its polymorphic association usingowner_idandowner_typecolumns. Theowner_typecolumn would contain the table of the address owner (eitherpeople,organizations, orevents) while theowner_idcolumn would contain the rowidof the address owner.
A major problem with the Active Record solution is that its logic is handled outside of the database in the ruby application. Active Record does not utilize a foreign key constraint for polymorphic associations because postgres can't handle that scenerio. i.e. the Hasura graphql-engine server would be handling the logic, checking for the existance of foreign records, etc. This strategy would likely run into major problems around concurrency and would be a major change for hasura, which seems to delegate as much logic as possible to the database.
Doing a bit of research, I've found a few helpful stackoverflow answers on how to handle polymorphic associations within the database:
- https://stackoverflow.com/a/4970646/5490505
- https://stackoverflow.com/a/7948179/5490505
The major takeaway is that postgres/sql does not formally support polymorphic associations. Given that polymorphic associations are a very common part of many data models though, and given that polymorphic associations are a widely used aspect of graphql, it seems worthwhile to find a way of supporting them within Hasura.
Since there doesn't appear to be an "official" way to support polymorphic associations, Hasura would need to take an opinionated route here (which, personally, I think is fine). The super-table / sub-table approach described in this Stackoverflow answer seems like a good solution: https://stackoverflow.com/a/4970646/5490505.
A super-table / sub-table approach would allow for describing both graphql interface types and union types (an interface type would be described by the super-table. A union type would also be described by the super table, but the super-table wouldn't contain any information other than id). Hasura would take care of joining the tables and presenting a single record to the graphql client. Hasura would also take care of handling mutations as if only a single table were being updated. Unfortunately, a super/sub table approach still wouldn't allow for cascade deletion of a polymorphic association (perhaps automatically generating postgres triggers could help with this).
Related: #1167
Laravel eloquent also has very nice polymorphic relations implemented https://laravel.com/docs/eloquent-relationships
@Youhan Looks like Laravel takes the same approach as ActiveRecord. As I pointed out in the "possible implementations" section above, while that strategy is pretty easy to implement at the graphql-engine level (i.e. not the database level), I think it's probably a non-starter for Hasura for the reasons outlined above.
Any updates on that?
Heyy, is it kosher to allow overriding of __typename, seems like we could easily get this to work, or is that too hacky?
TABLE Entity
id
TABLE entity_customer_types
entity_id
customer_id_for_type
__typename | <type goes here in text> eg. "CustomerType1"
TABLE CustomerType1
id
name
type_1_field
TABLE CustomerType2
id
name
type_2_field
########
# Usage:
########
Query {
Entity {
entity_customer_types {
__typename
... on CustomerType1 {
name
type_1_field
}
... on CustomerType2 {
name
type_2_field
}
}
}
}
I too come from a Laravel world, and really need polymorphic relations to make things like attaching images to models maintainable. Can we get some input on this becoming an official feature, or an option for the best temporary workaround? Thanks!
@zlanich Have you seen these workarounds? https://github.com/hasura/graphql-engine/issues/1167#issuecomment-444761777
@cllns Yes, and while that is "slightly" better than creating separate tables (from an organization perspective), it still requires you to create a relationship view for every object type. It's still quite clumsy, particularly when coming from a Laravel world where you don't need to do this. You just specify a morph-map that transforms the active record classes into slugs like "model-name", then it handles the rest for you. I'm really hoping for proper support for polymorphic relations at some point :(
I really like to see this feature. The absence actually keeps us from adopting hasura.
After reading a lot about this, I think the best solution is the one adopted by the GitLab team: always use separate tables instead of polymorphic associations. It's the only solution that ensures you don't need external logic and keep things atomic and organized.
@gloredo I initially interpreted your comment as, essentially, saying this issue should be closed without action. But on a second read, it's not clear to me that you are saying that. You might just be expressing the opinion that polymorphic relations (i.e. graphql union and interface types) should be built up within the Hasura server from multiple separate database tables.
Regardless, it should be noted that the article you linked to is largely specific to the Active Record (i.e. Rails) implementation of polymorphic associations and most of the criticisms discuss flaws specific to the Active Record approach. As I mentioned in the original post and also in a comment, I expect that the Active Record strategy is a no-go for Hasura, but that doesn't necessarily imply that Hasura couldn't achieve polymorphism via another method. Whatever Hasura does, I expect it will need to be opinionated since, so far as I'm aware, Postgres doesn't currently have a way to cleanly handle polymorphic associations (of course, I expect when Hasura supports MongoDB it will be easy to support polymorphic relations in that database via embedded documents).
But, in case other's interpret your comment like I originally did, I just wanted to point out that, even if we can't have polymorphic associations within the database, this doesn't mean that we can't have polymorphic relations within the GraphQL schema generated by Hasura. Perhaps I should make this clearer in the original posting above, but this is fundamentally a feature request for polymorphic relations within the generated graphql schema of Hasura. The possible implementations I discussed above focused on ways that this might be accomplished with the database schema, but if polymorphic graphql relations were achieved via another method this would also be fine (in my opinion, at least).
@jorroll, you're right, I expressed myself incorrectly. I strongly believe that hasura should implement a solution to the problem, but it should be as db-level as possible, and keeping the tables separate is the way to go in my opinion.
As a way to "representing" it, graphql union and interface types seem to make a lot of sense. Initially, I thought of a "virtual table" that the other tables would "extend" as the biggest problem with the separate tables approach is the duplication of columns between tables, but it doesn't seem like a pretty or viable solution.
RELATED:
-
graphQL spec RFC and
@oneOfhttps://github.com/graphql/graphql-spec/pull/825 This would eventually trickle down to hasura to figure out a way to implement -
General search on "polymorphism" in the graphql spec repo https://github.com/graphql/graphql-spec/issues?q=polymorphism+is%3Aopen
Any updates on this?
any updates?
is it possible to augment schema with polymorphic data without spinning up additional service with remote scheme (that's what AI suggested me to do in order to achieve desired result, I'm not sure if it's going to work this way, just starting to learn gql)?