Prisma1: [RFC] Datamodel v1.1 - Relations

Created on 1 Nov 2018  路  20Comments  路  Source: prisma/prisma1

This part of the spec describes the relations used to control relations. See the issue #3408 to learn about the other parts of the spec.

Updates since original post:

  • March 29th 2019: @linkTable was renamed to @relationTable

The embedded directive

  • valid locations: type
  • behaviour: Specifies a type as embedded, which means that the type will be stored along side its parent node. An embedded type has no identity itself. It can only be created or updated through nested mutations.
  • optional: yes. Defaults to treating a type as not embedded.
  • arguments: none
  • validation:

    • This directive is only allowed if the underlying database supports embedded types. This is the case for Document databases like Mongo.

Examples

The type Post is marked as embedded. In the underlying database there will be a document for each Blog and all related Posts will be stored inside it.

type Blog {
  id: ID! @id
  name: String!
  posts: [Post]
}

type Post @embedded {
  title: String!
}

The same example as before, but the embedded type Post is referenced by one more type.

type Blog {
  id: ID! @id
  name: String!
  posts: [Post]
}

type BestPostsOfTheYear {
  id: ID! @id
  year: Int!
  posts: [Post]
}

type Post @embedded {
  title: String!
}

The relation directive

  • valid locations: on relation fields
  • optional:

    • Yes if a relation is not ambiguous.

    • No if a relation is ambiguous.

    • A relation between two types is ambiguous if there is more than one relation between those two types.

  • arguments:

    • name: String



      • The name of the relation.


      • optional: Maybe. Defaults to an auto generated name, e.g. BlogToPost for a relation between Blog and Post. It is required if a custom relation table is specified. The name of relation must match the name of type that specifies the shape of the relation table.



    • onDelete: Enum | String



      • Specifies whether this relation is cascading or not. Cascading means that the related nodes get deleted when a node of the current type gets deleted.


      • optional: yes. Defaults to SET_NULL


      • valid values: SET_NULL, CASCADE, "SET_NULL", "CASCADE"



    • link: Enum



      • Specifies the mode that should be used to implement this relation.


      • optional: maybe. Based upon the arity of the relation and the connector a mode is chosen, e.g. TABLE for many to many relations in SQL. This is not always possible.


      • valid values:





        • INLINE: This mode stores the relation links within the current type.







          • The side of this value influences the layout of the database schema.




          • This is the only mode that is supported by Mongo.







        • TABLE: This mode creates a relation table. This is the default for many-to-many relations in SQL.





      • validation:





        • The mode must be only specified on one side of the relation.






  • validations:

    • If a relation is ambiguous and the name needs to be specified the directive needs to be put on both fields belonging to a relation.



      • If a name is provided voluntary it also needs to be on both sides of the relation.



    • The link argument must not be specified on both sides of a relation.

    • The link must be specified if it can not be inferred from the data model, e.g. for a 1:1 relation in a SQL database Prisma needs to know on which side the relation link should be stored.

Examples

Specifying a relation name for an ambiguous relation.

type Blog {
  id: ID! @id
  posts: [Post] @relation(name: "BlogToPost")
  posts2: [Post] @relation(name: "BlogToPost2")
}

type Post {
  id: ID! @id
  blog: Blog @relation(name: "BlogToPost")
  blog2: Blog @relation(name: "BlogToPost2")
}

Specifying a cascade for the type Blog.

type Blog {
  id: ID! @id
  posts: [Post] @relation(onDelete: CASCADE)
}

type Post {
  id: ID! @id
  blog: Blog
}

Specifying where a relation links gets stored. A document in the Blog collection will store an Array of Post ids. The directive could be moved to their opposite related field to store the relation links in the Post node.

type Blog {
  id: ID! @id
  posts: [Post] @relation(link: INLINE)
}

type Post {
  id: ID! @id
  title: String!
  blog: Blog
}

Forcing the TABLE mode for a one-to-may relation.

type Blog {
  id: ID! @id
  posts: [Post] @relation(link: TABLE)
  tags: [Tag]
}

type Post {
  id: ID! @id
  title: String!
  blog: Blog
}

Forcing the TABLE mode for a one-to-may relation and customising the relation table:

type Blog {
  id: ID! @id
  posts: [Post] @relation(link: TABLE name: "BlogsToPosts")
  tags: [Tag]
}

type Post {
  id: ID! @id
  title: String!
  blog: Blog
}

type BlogsToPosts @relationTable {
   blog: Blog!
   post: Post!
}

The relationTable directive

  • valid locations: on types
  • behaviour: This directive marks a type as a relation table. That means it is not a regular type that is included in our API. It simply describes the shape of a relation table.
  • optional:

    • The directive itself is not optional if a type represents a relation table.

    • A @relationTable type does not need to be specified if the relation table does follow our standard conventions.

  • arguments: none
  • validations:

    • A type that is marked as relationTable must:



      • not be referred in any relation field.


      • contain exactly 2 fields that refer the types that are involved in the relation.


      • must be referenced from a relation directive's name directive



    • The directive is only supported by SQL connectors.

Examples

An example of a many-to-many relation that follows our standard conventions. Therefore no relation table has to be specified explicitly. The name of the table will be _BlogToPost and the columns will be called A and B.

type Blog {
  id: ID! @id
  posts: [Post]
}

type Post {
  id: ID! @id
  blogs: [Blog]
}

An example of a many-to-many relation that does not follow our standard conventions. The name of the relation table and columns are customised.

type Blog {
  id: ID! @id
  posts: [Post] @relation(name: "BlogsToPosts")
}

type Post {
  id: ID! @id
  blogs: [Blog]
}

type BlogsToPosts @relationTable {
   blog: Blog!
   post: Post!
}
aremigrations aredatamodel rf2-accepted arenext

Most helpful comment

Hey! I've found myself on this proposal via the @edge type proposal which was closed and where it was stated that this new proposal would resolve the issue there.

The driving use case for the previous proposal is being able to store data on the relation edges between two nodes. A great example is storing the date an edge/relation was created.

I may be misreading this spec but I'm not sure how it resolves this specific use case. Would you be able to provide some insight here?

To clarify it seemed to be like @linkTable might've resolved this however then I saw that a) table relation link is not compatible with mongo and b) @linkTable must have exactly two fields.

All 20 comments

The link/INLINE and linkTable solve the problems I ran into. In the meantime, to work around not having them, I might use ID! field types and send multiple queries and join in the app code. So, to easily migrate the data when these features are released it would be good if I could specify the column names. (and it would make migration from other systems easier)

There's a slight misspelling in the relation directive, in the example for "Forcing the TABLE mode for a one-to-may relation."

@relation(mode: TABLE)

Should be

@relation(link: TABLE)

@lfades : Thanks 馃檹 . It's fixed now.

In the last example under the relation directive, shown again below, would the Blog.posts relation directive require a name: "BlogsToPosts" argument?

type Blog {
  id: ID! @id
  posts: [Post] @relation(link: TABLE)
  tags: [Tag]
}

type Post {
  id: ID! @id
  title: String!
  blog: Blog
}

type BlogsToPosts @linkTable {
   blog: Blog!
   post: Post!
}

@willm78 : This is not decided yet. Making it optional would mean users would have to know our internal defaults. What would you prefer?

@mavilein If you guys can make it look obvious, then that's good, otherwise it's better to be more verbose, probably new users will not see that as "obvious", one good idea there is to always use the name option in examples, but letting know the user that it can be optional, that way new users will not get confused and advanced users can take advantage of it.

@mavilein Personally, I think it's easier to learn new libraries when there's less implicit stuff to trip over. Auto-pluralizing names, for example BlogsToPosts, is a source of frustrating bugs to users who aren't familiar with the defualts. Mongoose is notorious for this https://github.com/Automattic/mongoose/issues/1350 and all of it could have been prevented if the library hadn't tried to be so smart. I prefer predictable any day of the week even if it sometimes might mean a little more boilerplate.

+1 for explicit over implicit.

And if you are going to keep pluralisation, then externalise how you do this.

I had to write some tooling to map between Type Names and pluralized version allType

Then we will require the relation name argument if link: TABLE is used. Thanks for the feedback! I was also leaning to explicitness but did not want to decide on my own :-)

INLINE: This mode stores the relation links within the current type.
  The side of this value influences the layout of the database schema.
  This is the only mode that is supported by Mongo.
TABLE: This mode creates a relation table. This is the default for many-to-many relations in SQL.

1) Does this mean that mongo connector does not support TABLE link, even for mant-to-many relations?

2) Postgresql connector can also use INLINE link for one-to-one and one-to-many relations (id stored as a foreign key in the table), right?

@beeplin :

  1. Yes. Mongo won't support that. But Mongo also does not need it to model many-to-many relations as it can store an array of ids in a document.
  2. Yes, that's correct.

@mavilein great!

I know the mongo connector is already available in 1.22, and all datamodel v1.1 issues are labeled as status/next. So will the postgres support for INLINE link be available in 1.23? ;)

@beeplin : No that is too early unfortunately. If things go well it could be available at the end of January.

Cool~ Really appreciate the great works of you guys:)

I'm no professional but I want to say to deal with hierarchical data we could reference the way TypeORM did; in fact, the so called "closure table", is the true adjacency list/relation table in Graph Theory.

The spec doc should be posted to Prisma.io.
I cannot even find these explanations on the office website

Any updates on timing or RPs/branches I can monitor?

Hey! I've found myself on this proposal via the @edge type proposal which was closed and where it was stated that this new proposal would resolve the issue there.

The driving use case for the previous proposal is being able to store data on the relation edges between two nodes. A great example is storing the date an edge/relation was created.

I may be misreading this spec but I'm not sure how it resolves this specific use case. Would you be able to provide some insight here?

To clarify it seemed to be like @linkTable might've resolved this however then I saw that a) table relation link is not compatible with mongo and b) @linkTable must have exactly two fields.

Hey @chrisui, are you referring to something like this? https://github.com/prisma/prisma/issues/4303

In Mongo is it possible to use @embedded and @relation on the same field ? This would work to save a catalog collection embedded and also to have a separate collection to modify/query easily the catalog.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

marktani picture marktani  路  3Comments

schickling picture schickling  路  3Comments

hoodsy picture hoodsy  路  3Comments

schickling picture schickling  路  3Comments

thomaswright picture thomaswright  路  3Comments