prisma / prisma

Next-generation ORM for Node.js & TypeScript | PostgreSQL, MySQL, MariaDB, SQL Server, SQLite, MongoDB and CockroachDB

Home Page:https://www.prisma.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support for Polymorphic Associations

piesrtasty opened this issue Β· comments

I have the following schema:

model Post {
  id            String    @default(cuid()) @id
}

model Comment {
  id            String         @default(cuid()) @id
}

model Vote {
  id            String         @default(cuid()) @id
  item       Post | Comment
}

I'd like to associate the Vote model so that a vote an be created for a Post or a Comment.

As it stands now I have multiple models, PostVote and CommentVote which seems less than ideal.

How would you model this on the database level?

@janpio on database level it could be

CREATE TABLE votes (
  ...
   item_id <what_ever_id_type>,
   item_type VARCHAR -- (or maybe ENUM) Post or Comment value
);

Yes I believe what @shaggyone suggested would work.

@janpio Is this going to be included in an upcoming release?

No, we are not working on this right now unfortunately. For now you have to build something yourself and work around this.

@janpio Okay, thanks for the update.

@janpio where does this fit on the roadmap for official support?

The current roadmap is more broad: https://www.prisma.io/docs/more/roadmap
This feature currently does not have a concrete plan, so also no ETA.

Closing in favor of: #2505.

Re-opening this issue. There's a long internal discussion for context.

This feature request is about Posts and Comments being Votable. Another example: Movies and books can both have reviews. They are reviewable. The Rails documentation explains it further.

We have another feature request for an Interface Type that would solve a similar problem. So far we thought about interface types only at the Prisma Level, where every field in the Vote interface must be in the Post and Comment models. Effectively, there is no concept of a vote at the database level and the vote data is stored in the Post and Comment tables.

Polymorphic associations do have a concept of a vote at the database level. This also makes votes queryable as a standalone entity.

Update: Sequelize has a better explanation of Polymorphic Associations.

@matthewmueller I think that the lib Ecto from the Elixir ecosystem does a great job at some of the issues of Laravel/Rails way of implementing such relationships, and how we can solve it: https://hexdocs.pm/ecto/polymorphic-associations-with-many-to-many.html

WDYT?

This is great news. I love Prisma, but I couldn't use it because it doesn't support this type of relationship. I'll switch completely when this support comes in.
I hope this work will be completed as soon as possible and we can start using it. Thank you for starting work on this.

Yeah already implemented Prisma assuming polymorphic relationships were a given... ouch.

This issue is 2 years old now and isn't even on the roadmap. I love prisma but I think lately they're concentrating their efforts on the wrong things.

Open source is open source. Make a PR ;-)

@andrewmclagan is not that simple. Prisma is not a random repo floating on GitHub. It is a product, with a team and founding. They do their fair share of advertising around the community too and I think that prisma itself is awesome.

What I'm worried about is that it seems like they're focusing resources on making the data proxy platform (which I assume they are gonna monetize in some way) or some other integration without making prisma itself more feature rich. There are a lot of things you can't do today apart from polymorphic relationships. What I'm trying to say here is that I would first try to have a more feature completed, rock solid product before moving away to some other company product. That way it would be a no-brainer for users to go with prisma with out giving it a second thought.

For example, we started a new project at my company about two month ago. I suggested prisma but they ended up going with TypeORM because some missing feature. So yeah, I could open a PR but it is easier for me or my team to just pick the tool that overcomes most challenges. Keep in mind that the prisma team is not composed by contributors but rather full-time employees.

Anyways, I don't mean to be the guy that complains, I just think a two year old issue that asks for a solution to a very common problem with no feedback / roadmap whatsoever from the team is not the way to go.

without making prisma itself more feature rich.

Don't worry about that, I think our changes in the last x months (see for example the release notes) or the list of the currently active preview features show that we are still very actively improving Prisma ORM. Polymorphic Associations just did not make the top of our priority list yet. (It would be great if we could do everything at the same time, but that is just not possible.)

@janpio Yes, I follow the release notes and the notion roadmap very closely. I want to be clear on one thing though, I love prisma and I still use it on every side project I have. What bothers me is that I can't suggest prisma to my company for more complex use cases.

What I see on the release notes is more data sources like mongo and cockroach which have been around a long time now (at least mongo), migrations improvements (which are very welcome) and some data proxy stuff (which I've tested myself and it is very cool). Referencial integrity was added for compatibility with plantescale if I'm not mistaken.

The thing is features like polymorphic relationships, geo-spacial queries, recursive queries, proper full-text indexes with algorithms like soundex, grouping by dates (months, years, etc) are the things that make prisma currently incomplete, and those features are very common for any kind of software. I think adding more datasources while shipping the same features is not very wise, quite the contrary, I would rather ship more features and when mature enough, add more datasources and ways to use prisma elsewhere.

Anyways, these are just my thoughts. I know you have limited resources, but I don't think this have to do with resources rather how the team is making the priority list.

Yes, but obviously we also made a conscious decision to prioritize this way in the last few months - it is not that we accidentally ended up like this, but we think this is the best way to make Prisma into a long term successful project.

MongoDB and CockroachDB are now both trending towards GA state, so we will switch back to "only" building features again soon. (Which we never stopped, btw, just the "Client" and "Query Engine" parts needed to focus on these a bit in the last few months - and even there we always try to slip in features when capacity allows).

Make sure all the things you mention have good features requests with good use case description and optimally potential APIs and designs - the better and easier it is to understand a feature, the easier time we have when we tackle these.

Make sure all the things you mention have good features requests with good use case description and optimally potential APIs and designs - the better and easier it is to understand a feature, the easier time we have when we tackle these.

Here is a list of open issues I consider would make prisma a lot more appealing to companies with more complex needs. Most of them have suggestions and ideas on how to implement them.

  • #2505 from 2020, 189 likes and several implementation proposal / suggestions.
  • #2789 from 2020, 207 likes.
  • #3725 from 2020, 46 likes.
  • #6653 from 2021, 43 likes.
  • #3398 from 2019, 42 likes.

Union Types: Every app comes to a point when some kind of feed or notification system is needed. So you have a model where the relation can be either A, B or C.

PostGIS: Any app working with some kind of geolocation.

Recursive Relations: Any app that has comments or product categories like an e-commerce.

Group by dates: Any app which wants to display a time based chart on the front-end, like a dashboard.

Soft deletes: Any app that don't want to permanently delete records from the database or implement some kind of trash can.

These issues are old enough, useful enough and have enough likes to make it to the roadmap.

Hello, I am new to using APIs and would need to learn how to use GraphQL APIs within Oracle scripts, procedures, and functions. Any resources - preferably tutorial videos on how to invoke GraphQL API to get data, and store it into a local Oracle table?
Thank you,
Kamal

I am too missing this set of features. Currently in a table of images which can be connected to several different entities. I tried a few work-arounds but it seems those doesn't work. Please excuse me if I misunderstood something, still getting into Prisma... Anyhow,

model Expense {
    id Int @id @default(autoincrement())
    // images Image[] // Not supported
}

model Remark {
    id Int @id @default(autoincrement())
    // images Image[] // Not supported
}
model Image {
    id           Int.      @id @default(autoincrement())
    remarkId     Int?      @default(0) // Prisma doesnt support polymorphism, this is a workaround
    expenseId    Int?      @default(0)
    // Expense   Expense?  @relation(fields: [expenseId], references: [id], onUpdate: SetNull)
    // Remark    Remark?   @relation(fields: [remarkId], references: [id], onUpdate: SetNull)

I first tried the commented out relations, the problem with that is that prisma sets cascading rules on both of the fields, even though they are optional. I simply cannot add a Image with expenseId > 0 and remarkId null. Those rules are triggered disallowing me to save data.

I ended up with a losely coupled schema which I try to demonstrate by keeping comments in place while waiting for these features to be implemented.

It's funny, I was googling to see if this was solved yet and found the issue I opened. I ran into another instance where I need polymorphic relations.

I ran into another instance where I need polymorphic relations.

@LukeHamilton What’s your current workaround for that?

Here is a solution that works for me:

model Profile {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title String
  sub String
  desc String
  address String
  tel String
  picture Picture? @relation("ProfilePicture")
}

model Competence {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title String
  desc String
  pictures Picture[] @relation("CompetencePicture")
}

model Project {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title String
  pres String
  desc String
  techno String
  link String
  picture Picture? @relation("ProjectPicture")
}

enum PicturableType {
  Competence
  Profile
  Project
}

model Picture {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  name String
  url String
  picturableId String  @unique
  picturableType PicturableType
  Project Project? @relation("ProjectPicture", fields: [picturableId], references: [id], map: "project_picturableId")
  Competence Competence? @relation("CompetencePicture", fields: [picturableId], references: [id], map: "competence_picturableId")
  Profile Profile? @relation("ProfilePicture", fields: [picturableId], references: [id], map: "profile_picturableId")
}

la doc

however it works with postgresql the enums don't work with sqllite so I guess there is another solution for sqllite (but I don't know about it)

Here is a solution that works for me:

model Profile {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title String
  sub String
  desc String
  address String
  tel String
  picture Picture? @relation("ProfilePicture")
}

model Competence {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title String
  desc String
  pictures Picture[] @relation("CompetencePicture")
}

model Project {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title String
  pres String
  desc String
  techno String
  link String
  picture Picture? @relation("ProjectPicture")
}

enum PicturableType {
  Competence
  Profile
  Project
}

model Picture {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  name String
  url String
  picturableId String  @unique
  picturableType PicturableType
  Project Project @relation("ProjectPicture", fields: [picturableId], references: [id], map: "project_picturableId")
  Competence Competence @relation("CompetencePicture", fields: [picturableId], references: [id], map: "competence_picturableId")
  Profile Profile @relation("ProfilePicture", fields: [picturableId], references: [id], map: "profile_picturableId")
}

la doc

however it works with postgresql the enums don't work with sqllite so I guess there is another solution for sqllite (but I don't know about it)

If this works, it is essentially a polymorphic relation and this issue can be closed!

@tonyea it’s not an ideal solution as every time a new row is created in the Picture table you end up with two null columns

@tonyeace n'est pas une solution idéale car chaque fois qu'une nouvelle ligne est créée dans le Picturetableau, vous vous retrouvez avec deux nullcolonnes

oh yes sorry, I forgot them? for it to work properly. I modified my code. Effectively this returns 2 null columns. I don't know if there is a solution without having null columns.

Hello,

After many tests my code above does not work. so i redid my code, i didn't want to leave something not working on github, here is my fix (which works this time)

I reviewed my logic because it was not good and therefore I leave the polymorphic case, so this code will bring you nothing but I wanted to clarify what I published.

some explanation:
my skills have several images, but profile and project have only one image, so I modified my tables:

skill n : 1 picture
project 1 : 1 picture
profile 1 : 1 picture

Here is the prisma code:

model Profile {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title String
  sub String
  desc String
  address String
  tel String
  pictureId String? @unique
  picture Picture? @relation(fields: [pictureId], references: [id])
}

model Competence {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title String
  desc String
  pictures Picture[]
}

model Project {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title String
  pres String
  desc String
  techno String
  link String
  pictureId String? @unique
  picture Picture? @relation(fields: [pictureId], references: [id])
}

model Picture {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  name String
  url String
  competenceId String?
  competence Competence? @relation(fields: [competenceId], references: [id])
  project Project?
  profile Profile?
}

this solution works for postgresql and sql lite
again there are null columns in all cases, I don't think it's a problem, there are always null columns in the database.

here is no polymorphic solution but at least the code above works ^^ .

if I come across a case where the polymorphic seems more appropriate to me, I will share my code, if the solution exists ^^

good for you

Here is a solution that works for me:

model Profile {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title String
  sub String
  desc String
  address String
  tel String
  picture Picture? @relation("ProfilePicture")
}

model Competence {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title String
  desc String
  pictures Picture[] @relation("CompetencePicture")
}

model Project {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title String
  pres String
  desc String
  techno String
  link String
  picture Picture? @relation("ProjectPicture")
}

enum PicturableType {
  Competence
  Profile
  Project
}

model Picture {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  name String
  url String
  picturableId String  @unique
  picturableType PicturableType
  Project Project? @relation("ProjectPicture", fields: [picturableId], references: [id], map: "project_picturableId")
  Competence Competence? @relation("CompetencePicture", fields: [picturableId], references: [id], map: "competence_picturableId")
  Profile Profile? @relation("ProfilePicture", fields: [picturableId], references: [id], map: "profile_picturableId")
}

la doc

however it works with postgresql the enums don't work with sqllite so I guess there is another solution for sqllite (but I don't know about it)

not work for Mysql :(

Another work around is to take care of the polymorphic relationship management in the application layer:

  • Database layer πŸ”΄
  • Prisma layer πŸ”΄
  • Application layer 🟒
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model Post {
  id    Int    @id @default(autoincrement())
  title String
  // Prisma lacks support for polymorphic relations and union types :-(
  // tags Tag[]
}

model Page {
  id    Int    @id @default(autoincrement())
  title String
  // Prisma lacks support for polymorphic relations and union types :-(
  // tags Tag[]
}

enum TaggableType {
  Page
  Post
}

model Tag {
  id           Int          @id @default(autoincrement())
  taggableId   Int
  taggableType TaggableType
  // Prisma lacks support for polymorphic relations and union types :-(
  // taggable Page | Post
}
import { PrismaClient, Tag, Post, Page } from "@prisma/client";

const prisma = new PrismaClient();

type Taggable = Post | Page;

/**
 * Get taggable for a tag
 */
function getTaggable(tag: Tag): Promise<Taggable> {
  if (tag.taggableType === "Post") {
    return prisma.post.findUniqueOrThrow({ where: { id: tag.taggableId } });
  }

  if (tag.taggableType === "Page") {
    return prisma.page.findUniqueOrThrow({ where: { id: tag.taggableId } });
  }

  throw new Error("Invalid taggable type.");
}

/**
 * Get tags for a taggable
 */
async function getTags(taggableId: number) {
  return prisma.tag.findMany({ where: { taggableId } });
}

Also possible to manually add constraints and cascades:

ALTER TABLE `Tag` ADD CONSTRAINT `TagPage_taggableId_fkey` FOREIGN KEY (`taggableId`) REFERENCES `Page`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE `Tag` ADD CONSTRAINT `TagPost_taggableId_fkey` FOREIGN KEY (`taggableId`) REFERENCES `Post`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;

RANT The more our team uses Prisma the more we realise its a well marketed but unfinished product. There are so many basic fundamental features of an ORM that Prisma is missing. We have lost all confidence in the product after initially investing a lot of time we are now considering rewriting our data layer into another ORM. Its hard to understand why basic features are not being prioritised over others that seem incredibly arbitrary.

It has so much potential, what it actually does it does it well, open-source community appreciates such projects but...

Don't market Prisma as a finished product when its not. That is misleading.

Disclaimer: sorry in advance for the negative comment
I was uncertain about posting this negative feedback, but I figured the Prisma team should be aware of how much grief they have caused my team and probably other ones.

In the last few years, I have seen a lot articles and podcasts speaking highly of Prisma.
So i decided to give it a try on a few small scale side projects and the DX was great.
More recently, when I created my company, I did a deep benchmark of the ecosystem before choosing my ORM.
From a theoretical point of view Prisma seemed like a perfect fit: a modern ORM, fully typed, backed by a well funded company and supposed to be production ready.

After investing months of work with several engineers, we had the same experience as @andrewmclagan.
We started to discover that Prisma was lacking basic features.
It might have a great DX but it's not fit for a production, real-world application.
The polymorphic association is a perfect example of that: it's a basic, standard, must have feature that any real-world application ends up having.

So, to be honest I feel betrayed by the shiny marketing of Prisma.
Usually most open source projects not backed by companies emphasize their limitations and their missing features.
They know that if users have to back away after investing tens of thousands of dollars, they are going to develop strong feelings against them. It doesn't make sense to force people to use their project because are not making any profit from it.
I thought that a project backed by a company (dedicated to the project) was a good sign but I'm starting to think otherwise.
I not so sure anymore that Prisma's interests are aligned with the users'.

Sorry again for the harsh feedback, but I would be happy to discuss in more details with the Prisma team.

@leonard-henriquez the same thing happened to me although I still use prisma for side / toy projects. Have you tried edgedb with their type-safe query builder? It has a similar dx but it gives you the full power of the db.

Hey @leonard-henriquez, thanks so much for sharing your feedback with us!

Disclaimer: sorry in advance for the negative comment

No need to be sorry, in fact it's much appreciated that you share it since that gives us an opportunity to improve πŸ™

It would be awesome if you could send me an email (on my GH profile page) so we can arrange a chat.

Many thanks from the Prisma team!

Another work around is to take care of the polymorphic relationship management in the application layer:

  • Database layer πŸ”΄
  • Prisma layer πŸ”΄
  • Application layer 🟒
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model Post {
  id    Int    @id @default(autoincrement())
  title String
  // Prisma lacks support for polymorphic relations and union types :-(
  // tags Tag[]
}

model Page {
  id    Int    @id @default(autoincrement())
  title String
  // Prisma lacks support for polymorphic relations and union types :-(
  // tags Tag[]
}

enum TaggableType {
  Page
  Post
}

model Tag {
  id           Int          @id @default(autoincrement())
  taggableId   Int
  taggableType TaggableType
  // Prisma lacks support for polymorphic relations and union types :-(
  // taggable Page | Post
}
import { PrismaClient, Tag, Post, Page } from "@prisma/client";

const prisma = new PrismaClient();

type Taggable = Post | Page;

/**
 * Get taggable for a tag
 */
function getTaggable(tag: Tag): Promise<Taggable> {
  if (tag.taggableType === "Post") {
    return prisma.post.findUniqueOrThrow({ where: { id: tag.taggableId } });
  }

  if (tag.taggableType === "Page") {
    return prisma.page.findUniqueOrThrow({ where: { id: tag.taggableId } });
  }

  throw new Error("Invalid taggable type.");
}

/**
 * Get tags for a taggable
 */
async function getTags(taggableId: number) {
  return prisma.tag.findMany({ where: { taggableId } });
}

Also possible to manually add constraints and cascades:

ALTER TABLE `Tag` ADD CONSTRAINT `TagPage_taggableId_fkey` FOREIGN KEY (`taggableId`) REFERENCES `Page`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE `Tag` ADD CONSTRAINT `TagPost_taggableId_fkey` FOREIGN KEY (`taggableId`) REFERENCES `Post`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;

RANT The more our team uses Prisma the more we realise its a well marketed but unfinished product. There are so many basic fundamental features of an ORM that Prisma is missing. We have lost all confidence in the product after initially investing a lot of time we are now considering rewriting our data layer into another ORM. Its hard to understand why basic features are not being prioritised over others that seem incredibly arbitrary.

It has so much potential, what it actually does it does it well, open-source community appreciates such projects but...

Don't market Prisma as a finished product when its not. That is misleading.

Adding multiple constraint using same column possible but not usable for relational database. Considering above example, taggableId can be either a Page or Post but not both at same time, therefore, same taggableId won't be present in both Post and Page at same time. Hence, any DB insert to page or post will fail with foreign key constraint.

Crazy that this is still open for such a commonly used feature in real world applications. I opened this issue before Covid started 🫠.

It's my most popular GH issue so I take solace in that. Hope we can get this feature shipped in 2023 πŸ₯³.

Imagine not giving your users something they are begging for

Imagine not giving your users something they are begging for

It's open source, if you want it so badly go ahead and submit a PR.

I am satisfied with everything while using Prisma, but I hesitate to use it because Prisma does not support polymorphic relationships.

Does the Prisma team have any plans to develop this in the future? I'd like to know a clear official position.

Yes, we will definitely look at implementing this in the future. But we can not say yet when that will happen and then be released.

I've been using Laravel for 8+ years and many of my projects extensively use many-to-many polymorphic relations. Recently, I was exploring Next.js so that I can make entire projects in one language and explored the ORMs. Chose Prisma as it seemed to have great DX, and was flashier compared to other websites and demos. 5-6 days into my code port from Laravel's Eloquent to Prisma, and I've hit a roadblock with the associations, and I'm now forced to look for alternatives. It would be great to see this implemented in Prisma.

Came across this today and the only thing that worked for me was a combination of this reply:

Hello,

After many tests my code above does not work. so i redid my code, i didn't want to leave something not working on github, here is my fix (which works this time)

I reviewed my logic because it was not good and therefore I leave the polymorphic case, so this code will bring you nothing but I wanted to clarify what I published.

some explanation: my skills have several images, but profile and project have only one image, so I modified my tables:

skill n : 1 picture project 1 : 1 picture profile 1 : 1 picture

Here is the prisma code:

model Profile {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title String
  sub String
  desc String
  address String
  tel String
  pictureId String? @unique
  picture Picture? @relation(fields: [pictureId], references: [id])
}

model Competence {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title String
  desc String
  pictures Picture[]
}

model Project {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title String
  pres String
  desc String
  techno String
  link String
  pictureId String? @unique
  picture Picture? @relation(fields: [pictureId], references: [id])
}

model Picture {
  id String @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  name String
  url String
  competenceId String?
  competence Competence? @relation(fields: [competenceId], references: [id])
  project Project?
  profile Profile?
}

this solution works for postgresql and sql lite again there are null columns in all cases, I don't think it's a problem, there are always null columns in the database.

here is no polymorphic solution but at least the code above works ^^ .

if I come across a case where the polymorphic seems more appropriate to me, I will share my code, if the solution exists ^^

good for you

  • the usage of the Prisma relation-mode instead of the PostgreSQL foreign keys. This is a horrible solution. I just hope that at some point Prisma will catch up on this.

At work, we stopped using Prisma for the many reasons other people have stated.

We created an alternative workaround using Prisma client's extensions feature. For the demo, I used the model shared by @nitinmuk .

import { Page, Post, Prisma, PrismaClient, Tag } from "@prisma/client";

type PageExtended = Page & { tags: Tag[] };

const prisma = new PrismaClient();

const xprisma = prisma.$extends({
  name: `polymorphism`,
  query: {
    page: {
      // Needs Prisma >= v4.7.0. Note: we need to cast the returned type in main().
      async findUnique({ args, query }): Promise<Partial<PageExtended>> {
        const page = await query(args);
        const tags = await prisma.tag.findMany({
          where: { taggableId: args.where.id },
        });

        return { ...page, tags };
      },
    },
  },
  model: {
    tag: {
      // Needs Prisma >= v4.9.0.
      async findFirstWithPolymorphism<
        Args extends Prisma.Args<T, "findFirst"> & {
          includeTaggable?: boolean;
        },
        T extends typeof prisma.tag
      >(
        this: T,
        args: Args
      ): Promise<
        | (Prisma.TagGetPayload<Prisma.TagArgs> &
            (Args["includeTaggable"] extends true
              ? NonNullable<Args["where"]>["taggableType"] extends "Post"
                ? {
                    post?: Post | null;
                  }
                : NonNullable<Args["where"]>["taggableType"] extends "Page"
                ? {
                    page?: Page | null;
                  }
                : {}
              : {}))
        | null
      > {
        const taggableId = args.where?.taggableId;
        const taggableType = args.where?.taggableType;

        const { includeTaggable } = args;
        delete args.includeTaggable;

        const tag = await this.findFirst(args);

        if (!tag) {
          return null;
        }
        if (taggableId == null) {
          return tag;
        }
        if (typeof taggableId !== "number") {
          throw new Error(
            "Provide a taggabled id number, object filters are not allowed."
          );
        }

        if (includeTaggable && taggableType === "Post") {
          const post = await prisma.post.findUnique({
            where: { id: taggableId },
          });
          return { ...tag, post };
        }
        if (includeTaggable && taggableType === "Page") {
          const page = await prisma.page.findUnique({
            where: { id: taggableId },
          });
          return { ...tag, page };
        }

        return tag;
      },
    },
  },
});

async function main() {
  const page = (await xprisma.page.findUnique({
    where: { id: 0 },
  })) as PageExtended | null;
  console.log("page is: ", page);

  const tagWithRelations = await xprisma.tag.findFirstWithPolymorphism({
    where: { id: 10, taggableType: "Post", taggableId: 0 },
    includeTaggable: true,
  });
  console.log("tagWithRelations is: ", tagWithRelations);
}

main();

Well, this has been a huge slap in the face. Likes, Comments, Versioned columns, all this jackpot I've implemented in my schema only to realise it wont work :(

What alternative to Prisma currently solves (all or part of) these problems?

@AxelBriche I'm pretty happy with edgedb

Does this suggestion not work? Marking the relationships with a map? Or does it only work with postgresql, and not with other DBs? Also, what would the queries and type resolution actually look like? With a Type or Kind it should be easy to discriminate the types in TS. Maybe I'll take the weekend to investigate.

#11108

@bcnichols3 not a great option as you will end up with null columns in that table

This issue is in my top 5 reasons to stop using prisma. I've been using prisma in my work for 2.5 years, and there are many basic features missing, but this one tops the list. This feature is a requirement for anyone who has a database with models such as assets (image, video, file) or interactions (likes, comments, shares) that join with multiple tables (post, profile, product). I still think prisma has the best node.js API for SQL, but this issue might be a dealbreaker the next time I'm building a DB.

Personally looking into Drizzle & Kysely as Prisma feels too lock-iny πŸ”’

The SQL-like syntax of Drizzle makes more sense to me and easier to work with

I'm going to unsubscribe from this issue since it has been three years now and there are no signs of it being near the roadmap. I hope the prisma team will consider listening a bit more to their users and deliver those basic missing features that we've been asking for years. With so many other cool projects such as edgedb or surrealdb, I don't think prisma can afford to stay still feature wise. Only time will tell.

i like prisma but without this not sure if i'm gona use it anymore. "Prisma makes working with databases easy" not sure if this statement is also correct.

@floelhoeffel four months ago when I discussed with you and the Prisma team you mentioned that you were on the verge to publish a documentation on how some teams succeeded to support polymorphic associations with Prisma extensions.
Is it still planned? Do you have any insights when it will be?
Thanks in advance !

@leonard-henriquez there is definitely an angle to polymorphic relations via extensions - at least on the Prisma client / code level. I haven't seen anyone publish content around it yet but we would love a pull request to https://github.com/prisma/prisma-client-extensions in case someone has bandwidth to explore this area πŸ™

Trying to migrate from mongoose and sequelize to prisma with 'complex feature' but not with 'simple feature' like this. I don't think I would waste my time to wait.

@janpio does this label means that you are taking an interest on this topic? πŸ₯³ πŸŽ‰ 😍

Just to sum up there are several issues related to polymorphism left un-answered:

  • #1644 (55 comments and a few hundreds upvotes)
  • #253 (20 comments)
  • #2505 (73 comments and more than 500 hundreds on the first message only)
  • #2506 (11 comments)
  • #4173
  • #15554

I am fully available days and nights if you want to have some insights on:

  • why it's a major blocking issue for any SaaS product that need tags, comments, files (or any model that need to be shared across the codebase) because they are no hints of solutions (even dirty ones)
  • what would be necessary to unblock this situation

Fundamentally it just means that the issue was missing the label that signals to us that this also has an influence on our Schema Management domain and team. I am aware of these issues and their upvotes, and the comments - each and every one send me a GitHub notification. Unfortunately, that does not directly translate into having design and development capacity available to tackle this. As soon as that is available, this issue might make it into our list of priorities. Good chance for ... yeah I won't name a timeframe now - otherwise if things go sideways, someone will remind me of having lied afterwards πŸ˜†

So to make it explicit: We are aware this is a needed feature. We would love to work on it. But not possible in this moment.

@janpio

We would love to work on it. But not possible in this moment.

Genuinely curious as to what is needed to maybe help this along?

Straightforward answer is that you can't. No one here is available to jump into any discussions right now, no one is available to review any PRs that try to partially implement this, and no one is available right now to start the design or implementation work. (If you can help out in other issues to make sure the problem is described, a minimal reproduction is available and so on that theoretically saves us some time - but that only very indirectly translates to benefits for this set of issues.)

This was misleadingly formulated. See #21099 (reply in thread) for an update with better wording and a suggestion on how to help this along.

@janpio -- Polymorphic associations were added to Rails 1.1 in March 2006. I understand Prisma needs to monetize and that will be easier with new paid features, but can't we at least add polymorphic to the roadmap?

To sumup :

  • It's a standard feature (it's in sequelize, it was in Rails 1.1, etc)
  • It's a blocking feature: there is no documented (or even undocumented) workaround
  • It's one the most virulent issue (top 1% of the most active issues of this repo ever)
  • There is no ETA
  • There's nothing we can do to accelerate this process

@janpio what does the Prisma team advice to a team that needs polymorphic association ?

@janpio what does the Prisma team advice to a team that needs polymorphic association ?

I'm not from Prisma and this solution is not not ideal, but it worked for me, no complaints.
Allows me to keep likes for both Comments and Pictures in the same table.

model Like {
  id             String  @id @default(cuid())
  pictureId      String?   // Either this is not null..
  commentId      String?   // ..or this. 
  userId         String    // User who liked.

  user       User        @relation(fields: [userId], references: [id], onDelete: Cascade)
  picture    Picture?    @relation(fields: [pictureId], references: [id], onDelete: Cascade)
  comment    Comment?    @relation(fields: [commentId], references: [id], onDelete: Cascade)

  @@unique([pictureId, userId])
  @@unique([commentId, userId])
}

Thank you @anri-asaturov but it's not really an option when your "Like" table can be related to 10+ other tables:

  • It's hard to maintain constraints to have exactly one id that is not null (pictureId XOR commentId XOR ...)
  • You need to add tons of conditions everywhere which introduce a lot of tech debt
  • It's not typesafe
  • Joins become very very verbose

I follow the db structure of Laravel's ORM when it comes to doing this as a workaround in Prisma.

model Post {
  id             String  @id @default(cuid())
  title          String
  ...
 }

model Comment {
  id             String  @id @default(cuid())
  body        String
  ...
}

enum LikeType {
  Comment
  Post
}

model Like {
  id                    String  @id @default(cuid())
  likeableType   LikeType @default(Post);
  likeableId       String
}

Then I just do a separate query on Like table to grab likes for whatever resource I'm getting be it a Comment or Post.

Polymorphic Associations announced in Prisma 17. Expected release: 2031.

Straightforward answer is that you can't. No one here is available to jump into any discussions right now, no one is available to review any PRs that try to partially implement this, and no one is available right now to start the design or implementation work. (If you can help out in other issues to make sure the problem is described, a minimal reproduction is available and so on that theoretically saves us some time - but that only very indirectly translates to benefits for this set of issues.)

Is this still the case? Happy to help...

I follow the db structure of Laravel's ORM when it comes to doing this as a workaround in Prisma.

model Post {
  id             String  @id @default(cuid())
  title          String
  ...
 }

model Comment {
  id             String  @id @default(cuid())
  body        String
  ...
}

enum LikeType {
  Comment
  Post
}

model Like {
  id                    String  @id @default(cuid())
  likeableType   LikeType @default(Post);
  likeableId       String
}

Then I just do a separate query on Like table to grab likes for whatever resource I'm getting be it a Comment or Post.

Is this method possible with a database that doesn't support enums? I'm using MSSQL server

commented

Any update on this topic?

@janpio I need to make a tech decision for my company. Either to go on with Prisma or to switch to another ORM.
The amount of monkey patching and work-arounds we have in the code base because of this lack of support for Polymorphism and UnionType has reach its threshold of maintainability.

Will this feature make it in the priorities in the coming 6 months ?

@Sohett If I were you, I would go with another solution. There has been no evidence this is a priority or will be in the near future, as unfortunate as that is.

commented

To be honest, this is a must feature for any medium+ sized project. Currently, you have to either:

  1. Make tons of specific tables just to comply to prisma relation capabilities (which is extremely bad practice and so hard to maintain).
  2. Write raw sql to join and fetch the data yourself which basically makes prisma obsolete in the first place.
  3. Write additional queries to join the data in code (which is terrible for maintenance, not to mention the amount of queries you have to execute). Furthermore, you might have upwards of 5 such relations for a single query/entity which make complexity unbearable.

Needless to say, all of them are terrible workarounds.

@Sohett If I were you, I would go with another solution. There has been no evidence this is a priority or will be in the near future, as unfortunate as that is.

This is so sad @piesrtasty ! I love Prisma, but it's not really an option for "real life production projects" then. How do you guys work-around this in your projects (real business projects I mean) ?

  • I mean, don't we all have a Media table that is related to multiple other objects (a message, a contact, ...) ?
  • A notification or activity feed that can be of many types ?
  • An address table that is either related to a person, a company, or a credit card ?

I can't believe that Prisma is only used for side projects and POC ! How is everyone else doing ?

commented

@Sohett If I were you, I would go with another solution. There has been no evidence this is a priority or will be in the near future, as unfortunate as that is.

This is so sad @piesrtasty ! I love Prisma, but it's not really an option for "real life production projects" then. How do you guys work-around this in your projects (real business projects I mean) ?

  • I mean, don't we all have a Media table that is related to multiple other objects (a message, a contact, ...) ?
  • A notification or activity feed that can be of many types ?
  • An address table that is either related to a person, a company, or a credit card ?

I can't believe that Prisma is only used for side projects and POC ! How is everyone else doing ?

Let alone the business context, you can't even have a simple Tags table.

We don't use prisma for production stuff because of this. :(

@Sohett We don't communicate concrete dates, so I also can't promise we will get to this in the next 6 months. If you absolutely need this, your better choice is indeed to use another tool. We hope to win you back when we implement this in the future.

We can't help but feel genuinely disappointed to see Prisma's focus shift away from its foundational open-source ORM to building new paid cloud tools. While we understand that business goals evolve, this shift seems like a missed opportunity to foster deeper community engagement. If improving your ORM is no longer your primary focus, we'd love to see the door open for more community contributions in this area.

It's been a disheartening year for the open-source community, with events like Terraform changing its license only adding to our concerns. The recent developments at Prisma further deepen those concerns. We sincerely hope there's room to reconsider this approach for the betterment of all involved. πŸ₯²

While there are a number of missing features in Prisma that don't have a good workaround, this doesn't seem like one to me? In the case of a 1:many like comments, create a commentable table such that, say, post and video have a 1:1 with commentable and then the commentable record has 1:many with comments:

model Post {
  id String  @id @default(cuid())
  commentableId String
  commentable Commentable @relation(fields: [commentableId], references: [id])
}

model Video {
  id String  @id @default(cuid())
  commentableId String
  commentable Commentable @relation(fields: [commentableId], references: [id])
}

model Commentable {
  id String  @id @default(cuid())
  comments Comment[]
  post Post
  video Video
}

model Comment {
  id String  @id @default(cuid())
  commentableId String
  commentable Commentable @relation(fields: [commentableId], references: [id])
}

For a many:many like tags, add a join table

model Post {
  id String  @id @default(cuid())
  taggableId String
  taggable Taggable @relation(fields: [taggableId], references: [id])
}

model Video {
  id String  @id @default(cuid())
  taggableId String
  taggable Taggable @relation(fields: [taggableId], references: [id])
}

model Taggable {
  id String  @id @default(cuid())
  tagSet tagSet[]
  post Post
  video Video
}

model TagSet {
  id String  @id @default(cuid())
  taggableId String
  taggable Taggable @relation(fields: [taggableId], references: [id])
  tagId String
  tag Tag @relation(fields: [tagId], references: [id])
}

model Tag {
  id String  @id @default(cuid())
  tagSets TagSet[]
}

While it does mean you can potentially have "orphan" commentable/taggable entries that aren't connected to anything, I think that's preferable over not having a foreign key to ensure the validity of the relationship. And when querying "in reverse" you do still have to have sparse reverse relations (ie, in TS, not in the DB), but since you don't know what is on the other side anyways, that seems reasonable. I suppose returning a single item as a union would be a bit cleaner, but again personally I'd prefer having that foreign key. Minimally, this allows you to properly query the relation without a large number of null columns.

We can't help but feel genuinely disappointed ...

I am responding to this message in a discussion, as it is not related to support of Polymorphic Associations at all and will be irrelevant for everyone subscribed to this issue: #21099 Please, redirect any further responses to this to the GitHub discussion instead. Keep this comment thread for responses to the support of Polymorphic Associations. Thank you.

@janpio any news or plans you could share here?

No change since I posted above 1 month ago: #1644 (comment)

I was a subscriber to this issue for a year, now it is time to move on. I can't wait my whole life. Maybe, someday, my sun could come here and see that this is added. Goodbye for now.

I was a subscriber to this issue for a year, now it is time to move on. I can't wait my whole life. Maybe, someday, my sun could come here and see that this is added. Goodbye for now.

It's been so long that it's kind of fun at this point, like how long can they go without adding this feature.

Hey I just found out about this thread. Would love to be part of the discussion :)

@janpio but maybe you could explain why?

Looking at this thread it is clear this is something people need, and when we combine that with the fact you guys are shipping stuff fairly frequently - it is sort of astounding to not have an ETA or at least a clear commitment.

@janpio based on the discussion here - #21099 you mentioned you're open to proposals for implementation? Happy to delve into this if the submission will be taken seriously post the next milestone release (which I assume is 5.5?)

@ydennisy What exactly do you want explained?

We are busy building and shipping other things that are higher in our priority list (see our public roadmap that shows what we are currently working on at https://pris.ly/roadmap or see the last few releases what we are working on). We also have many other things that are highly requested (for example more πŸ‘ or comments than this one). We don't know exactly when polymorphic associations will make the top of the list so that we can actually invest the time to figure this out, design a solution, share it with you, and then implement it. That means it is impossible for us to give an ETA. That this issue exists and has not been close is a clear commitment from us that we consider this relevant and will look into it when we do have capacity.

Any other things I could respond to or clarify?

@jackcoup Yes! I very much prefer messages here that start with something like "I looked into this topic, and think this is how this could be implemented in Prisma: ...". That would be amazing and would make it much easier for us to get started - when we do. (Often a good design proposal leads to a good discussion, which leads to better understanding - and then often much quick uptake of the feature as it is well understood and can be implemented).

[That would even happen outside of any release, I am posting here, although we are working on 5.5 right now. But of course, it would not mean we can jump on this instantly and ship it in a version or two. But the discussion would be more useful and move forward for sure.]

I already set up prisma when finding this issue. I didn't assume such an essential feature is not supported. It's definitely a blocking issue for prisma further adoption, besides soft deletes - although softdeletes are easier to ship around.

I also researched other ORMs in the Typescript ecosystem:

ORM name Polymorphic Relationship supported Comment
Prisma ❌
Drizzle πŸ§‘β€πŸ’» hacky solution here
TypeORM πŸ§‘β€πŸ’» limited through this package
Sequelize πŸ§‘β€πŸ’» hacky solution here
MikroORM πŸ§‘β€πŸ’» inheritance-based, doc here
Bookshelf.js πŸ§‘β€πŸ’» not typescript, doc here
Objection.js πŸ§‘β€πŸ’» not typescript, doc here

βœ… = first class support, πŸ§‘β€πŸ’» = supported, but really hacky or non-typed, ❌ = unsupported

Please let me know if there are any others. It seems weird to me, that there is so less adoption of this in the Typescript/Node.js ecosystem. πŸ€”

@simonsan here's how you can do polymorphic associations in drizzle drizzle-team/drizzle-orm#1051 (comment)

And here a possible solution in rdb
alfateam/orange-orm#58 (comment)

Haven't really looked into TypeORM

@simonsan actually most ORMs support polymorphic associations

ORM name Polymorphic Relationship supported
Prisma ❌
Drizzle πŸ†— (solution here)
TypeORM πŸ†— (through this package)
MikroORM βœ… (doc here)
Sequelize βœ… (doc here)
Bookshelf.js βœ… (doc here)
Objection.js βœ… (doc here)

@simonsan actually most ORMs support polymorphic associations
ORM name Polymorphic Relationship supported
Prisma ❌
Drizzle πŸ†— (solution here)
TypeORM πŸ†— (through this package)
MikroORM βœ… (doc here)
Sequelize βœ… (doc here)
Bookshelf.js βœ… (doc here)
Objection.js βœ… (doc here)

Last days I toyed around with Sequelize and after that I wouldn't say any more, that it is supported. It feels like a hack, you need to work with hooks basically. Native support should be as easy as defining a M:N-relation. Do you know one where that is the case? Because, I'm honestly losing hope here with the Typescript ecosystem. I just heard it from other people, but the last week or two I really experienced it myself, that Typescript has nothing to do in a more complicated backend, and I'm shortly before going back to - subpar PHP and Eloquent. Which I also don't like as a language, but this is what the project initially was written in.

EDIT: And I'm not talking about some inheritance-based or multiple foreign-keys polymorphic association. I'm talking about: https://sequelize.org/docs/v7/associations/polymorphic-associations/#single-model-single-foreign-key-polymorphic-associations

Just to make sure to state what I call 'supported': https://laravel.com/docs/10.x/eloquent-relationships#one-to-many-polymorphic-relations

It means first class support without 200 LOC hacky solutions that may break each version:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
 
class Comment extends Model
{
    /**
     * Get the parent commentable model (post or video).
     */
    public function commentable(): MorphTo
    {
        return $this->morphTo();
    }
}
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;
 
class Post extends Model
{
    /**
     * Get all of the post's comments.
     */
    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;
 
class Video extends Model
{
    /**
     * Get all of the video's comments.
     */
    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

The question for me is just: Can we have nice things as that in Typescript as well?

Hey guys! We are building ZenStack on top of Prisma. One of ZenStack's goals is to enhance the Prisma schema by adding features like access policies, customer attributes and model inheritance. Polymorphism is our next target.

We understand that this is a non-trivial task, so before proceeding with the implementation, we have written a post to explain our approach. We would greatly appreciate hearing your opinions on it:
https://zenstack.dev/blog/polymorphism

Please leave your thoughts about the proposal in this GitHub issue or simply join our Discord to chat.

@jiashengguo is this what we call the christmas magic? 😍

Hi, this is indeed an important feature for anyone who works on a forum-like system where Like, Favourite can assoiate with many tables like Post, Comment, Category, Topic, etc.

I believe this was an essential feature since that majority of ORMs for Node.JS have this feature. Recently I have migrated to Prisma and found out prisma does not have this feature supported. As well as custom value ENUM support.

I hope you guys can put this into the roadmap, so we can hope for it. Without this feature, I think many others have to choose another ORMs.

Would like to see this too. Not having this feature will be a major issue if my project scales.

It's been 4 years. I don't think we're gonna get this feature.

@ian prisma is no longer the energetic prisma 2020

Not an advertisement, but I would like to suggest Prisma's team to take a look at Sequelize's document and source code, they have done a perfect job in explaining and developing polymorphic assosication. Sequelize support three types of polymorphic association. I believe Single-model, single-foreign-key polymorphic associations is the more common way to achieve it using a type field and an id field which I have mentioned above.

kind of getting polymorphic relationship by just using an enum/types of models + a modelid

from the issue example:

model User {
  id            String    @default(cuid()) @id
  votes         Vote[]
  posts         Post[]
  comments      Comment[]
  ...
}

model Post {
  id            String    @default(cuid()) @id
  ...
}

model Comment {
  id            String    @default(cuid()) @id
  ...
}

model Vote {
  id            String    @default(cuid()) @id
  voteId        String    <-- tracking item through id of comment or post
  vote          VoteType  <-- tracking model type
  User          User?     @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId        String?
  ...
}

enum VoteType {
 POST
 COMMENT
}

downside is it involves a bit more client side managment

all in all though, this is still open for 4 years.. sheesh.

this issue is very similar to another one I ran into with prisma-- being able to split my schema which should be a given.

#2377

Also been open for 4 years.

from the comments of both this issue and the one i listed here, community has noticed the huge shift towards monitization- away from basic maintaince of prisma

rip

commented

While there are a number of missing features in Prisma that don't have a good workaround, this doesn't seem like one to me? In the case of a 1:many like comments, create a commentable table such that, say, post and video have a 1:1 with commentable and then the commentable record has 1:many with comments:

model Post {
  id String  @id @default(cuid())
  commentableId String
  commentable Commentable @relation(fields: [commentableId], references: [id])
}

model Video {
  id String  @id @default(cuid())
  commentableId String
  commentable Commentable @relation(fields: [commentableId], references: [id])
}

model Commentable {
  id String  @id @default(cuid())
  comments Comment[]
  post Post
  video Video
}

model Comment {
  id String  @id @default(cuid())
  commentableId String
  commentable Commentable @relation(fields: [commentableId], references: [id])
}

For a many:many like tags, add a join table

model Post {
  id String  @id @default(cuid())
  taggableId String
  taggable Taggable @relation(fields: [taggableId], references: [id])
}

model Video {
  id String  @id @default(cuid())
  taggableId String
  taggable Taggable @relation(fields: [taggableId], references: [id])
}

model Taggable {
  id String  @id @default(cuid())
  tagSet tagSet[]
  post Post
  video Video
}

model TagSet {
  id String  @id @default(cuid())
  taggableId String
  taggable Taggable @relation(fields: [taggableId], references: [id])
  tagId String
  tag Tag @relation(fields: [tagId], references: [id])
}

model Tag {
  id String  @id @default(cuid())
  tagSets TagSet[]
}

While it does mean you can potentially have "orphan" commentable/taggable entries that aren't connected to anything, I think that's preferable over not having a foreign key to ensure the validity of the relationship. And when querying "in reverse" you do still have to have sparse reverse relations (ie, in TS, not in the DB), but since you don't know what is on the other side anyways, that seems reasonable. I suppose returning a single item as a union would be a bit cleaner, but again personally I'd prefer having that foreign key. Minimally, this allows you to properly query the relation without a large number of null columns.

How would you approach cascade delete scenario for these relations? Deleting Video/Post should delete all related comments. The case for many-to-many is even more dire.