prisma / prisma

Next-generation ORM for Node.js & TypeScript | PostgreSQL, MySQL, MariaDB, SQL Server, SQLite, MongoDB and CockroachDB
https://www.prisma.io
Apache License 2.0
38.96k stars 1.53k forks source link

Prisma resolves only the last element of a nested `include` query #7713

Closed nizamiza closed 2 years ago

nizamiza commented 3 years ago

Bug description

Not sure if I'm missing something or there is a bug.

I have a basic localization design for a Project entity (including only relevant parts of the schema):

Prisma schema ```prisma model Partner { revisionId BigInt @id @default(autoincrement()) @map(name: "revision_id") id Int @default(autoincrement()) createdAt DateTime @default(now()) @map(name: "time") @db.Timestamptz(6) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6) name String logo String? projectPartnerTypes ProjectPartnerType[] @@map(name: "audit_organization") } model Project { id BigInt @id @default(autoincrement()) createdAt DateTime @default(now()) @map(name: "created_at") @db.Timestamptz(6) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6) published Boolean @default(false) translations ProjectTranslation[] @@map(name: "projects") } model ProjectPartnerType { id BigInt @id @default(autoincrement()) createdAt DateTime @default(now()) @map(name: "created_at") @db.Timestamptz(6) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6) priority Int @default(1) name String projectTranslationId BigInt @map("project_translation_id") projectTranslation ProjectTranslation? @relation(fields: [projectTranslationId], references: [id]) partners Partner[] @@map(name: "project_partner_types") } model ProjectTranslation { id BigInt @id @default(autoincrement()) projectId BigInt @map(name: "project_id") locale Locale name String contactEmail String? @map(name: "contact_email") partnerTypes ProjectPartnerType[] project Project? @relation(fields: [projectId], references: [id]) @@unique([projectId, locale]) @@map(name: "project_translations") } ```

I have a resolver for the Project entity:

GraphQL resolver ```typescript project: async ( _root: any, { id, published, locale }: QueryProjectArgs ): Promise => prisma.project.findFirst({ where: { AND: [ { id }, published === true ? { published: true, } : {}, ], }, include: { translations: { where: locale ? { locale } : {}, include: { partnerTypes: { include: { partners: true, }, }, }, }, }, }), ```

Application has 2 locales: sk-SK and cs-CZ. And if you don't pass the locale param to this resolver, it should return project with all translations. Which it does, except it doesn't resolve nested partners inclusion for all translations, it does so only for the last element of the resulted project.translations array. Below are sample queries with their results:

1. When locale is specified

query {
  project(id: 20, locale: sk) {
    translations {
      id
      partnerTypes {
        id
        partners {
          id
          name
        }
      }
    }
  }
}
Result ```json { "data": { "project": { "translations": [ { "id": 39, "partnerTypes": [ { "id": 108, "partners": [ { "id": 5, "name": "Johns and Sons" }, { "id": 12, "name": "Pollich - Zieme" }, { "id": 14, "name": "Grant, Cole and Waelchi" } ] }, { "id": 109, "partners": [ { "id": 1, "name": "Willms - Renner" }, { "id": 4, "name": "Kiehn, Mante and Greenholt" }, { "id": 6, "name": "Miller, Kohler and Doyle" }, { "id": 9, "name": "Goodwin - Stokes" }, { "id": 10, "name": "Mayert - Morissette" }, { "id": 13, "name": "King - Bergnaum" }, { "id": 17, "name": "Morissette - Grimes" } ] }, { "id": 110, "partners": [ { "id": 7, "name": "Schaden and Sons" }, { "id": 16, "name": "Medhurst Group" }, { "id": 18, "name": "Huels - Lemke" } ] } ] } ] } } } ```

Partner type ids: 108, 109, 110.

2. When locale is not specified

Same query without locale param:

query {
  project(id: 20) {
    translations {
      id
      partnerTypes {
        id
        partners {
          id,
          name
        }
      }
    }
  }
}
Result ```json { "data": { "project": { "translations": [ { "id": 39, "partnerTypes": [ { "id": 108, "partners": [] }, { "id": 109, "partners": [] }, { "id": 110, "partners": [] } ] }, { "id": 40, "partnerTypes": [ { "id": 111, "partners": [] }, { "id": 112, "partners": [ { "id": 1, "name": "Willms - Renner" }, { "id": 4, "name": "Kiehn, Mante and Greenholt" }, { "id": 5, "name": "Johns and Sons" }, { "id": 6, "name": "Miller, Kohler and Doyle" }, { "id": 8, "name": "Reynolds, Vandervort and Abbott" }, { "id": 9, "name": "Goodwin - Stokes" }, { "id": 10, "name": "Mayert - Morissette" }, { "id": 11, "name": "Schinner - Spencer" }, { "id": 12, "name": "Pollich - Zieme" }, { "id": 13, "name": "King - Bergnaum" }, { "id": 14, "name": "Grant, Cole and Waelchi" }, { "id": 15, "name": "Tillman, Fritsch and Raynor" }, { "id": 17, "name": "Morissette - Grimes" } ] }, { "id": 113, "partners": [ { "id": 7, "name": "Schaden and Sons" }, { "id": 16, "name": "Medhurst Group" }, { "id": 18, "name": "Huels - Lemke" } ] } ] } ] } } } ```

I feel like I'm missing something obvious here...

How to reproduce

  1. Create a schema as described above.
  2. Write a resolver with a nested include.
  3. Run resolver.
  4. Receive incomplete data.

Expected behavior

To get all data, including deeply nested fields.

Prisma information

Environment & setup

Prisma Version

v3.3.0
pantharshit00 commented 3 years ago

Hello @Hazeman28

Sorry for the late reply here. Can you still reproduce this with 2.28? Also, try passing where: locale ? { locale } : undefined instead of where: locale ? { locale } : {},

If you can still reproduce this, please share a sql dump of these tables which we can use in a reproduction.

nizamiza commented 3 years ago

Hello @Hazeman28

Sorry for the late reply here. Can you still reproduce this with 2.28? Also, try passing where: locale ? { locale } : undefined instead of where: locale ? { locale } : {},

If you can still reproduce this, please share a sql dump of these tables which we can use in a reproduction.

Hi, @pantharshit00 👋

Thanks for replying. I've tested locale ? { locale } : {} vs locale ? { locale } : undefined, but issue still persists.

I've also upgraded to v2.28, but I'm still having the same problem. I've encountered this issue on another entity model in our project. I will describe details below.

Related schema portion

Prisma schema ```prisma model ArticleCategory { id BigInt @id @default(autoincrement()) title String @unique updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6) articles Article[] @@map(name: "article_categories") } model ArticleSubcategory { id BigInt @id @default(autoincrement()) title String @unique updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6) articles Article[] @@map(name: "article_subcategories") } model Article { id BigInt @id @default(autoincrement()) createdAt DateTime @default(now()) @map(name: "date") @db.Timestamp(6) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6) publishedAt DateTime? @map("published_at") @db.Timestamptz(6) title String? subtitle String? mainImage String? @map(name: "main_image") text String? published Boolean? tag String? categories ArticleCategory[] subcategories ArticleSubcategory[] tags String[] secondaryTags String[] @map(name: "secondary_tags") userId BigInt? @map(name: "user_id") author User? @relation(fields: [userId], references: [id]) homeArticles HomeArticle[] @@map(name: "articles") } ```

GraphQL resolvers

Single article ```typescript article: async ( _root: any, { id }: QueryArticleArgs ): Promise
=> prisma.article.findUnique({ where: { id, }, include: { author: true, categories: true, subcategories: true, }, }), ```
Many articles with a filter ```typescript articles: async ( _root: any, { filter }: QueryArticlesArgs ): Promise => { const where = { AND: [ filter?.searchString ? { title: { contains: filter.searchString, mode: "insensitive" as Prisma.QueryMode, }, } : {}, filter?.published ? { published: filter.published, } : {}, !isEmpty(filter?.tags) ? { OR: [ { tag: { in: filter?.tags ?? undefined, }, }, { tags: { hasSome: filter?.tags ?? undefined, }, }, { secondaryTags: { hasSome: filter?.tags ?? undefined, }, }, ], } : {}, !isEmpty(filter?.categories) ? { categories: { some: { id: { in: filter?.categories ?? undefined, }, }, }, } : {}, !isEmpty(filter?.subcategories) ? { subcategories: { some: { id: { in: filter?.subcategories ?? undefined, }, }, }, } : {}, ], }; const [totalCount, data] = await prisma.$transaction([ prisma.article.count({ where }), prisma.article.findMany({ ...getPrismaPaginationFields(filter), where, orderBy: { publishedAt: "desc", }, include: { author: true, categories: true, subcategories: true, }, }), ]); }, ```

The problem

Fetching single article works as expected:

query {
  article(id: 1) {
    id
    categories {
      title
    }
    subcategories {
      title
    }
  }
}
Result ```json { "data": { "article": { "id": 1, "categories": [ { "title": "Ešport" }, { "title": "Hry" }, { "title": "Iné" } ], "subcategories": [ { "title": "Novinky" }, { "title": "Recenzie" }, { "title": "Podujatia" }, { "title": "SK/CZ" }, { "title": "Zahraničie" }, { "title": "Previews" }, { "title": "Súťaže" }, { "title": "Rozhovory" }, { "title": "PR články" } ] } } } ```

Problem appears when fetching many articles:

query {
  articles(filter: { perPage: 3 }) {
    data {
      id
      categories {
        title
      }
      subcategories {
        title
      }
    }
  }
}
Result ```json { "data": { "articles": { "data": [ { "id": 1, "categories": [], "subcategories": [ { "title": "Novinky" } ] }, { "id": 2, "categories": [], "subcategories": [ { "title": "Recenzie" }, { "title": "Podujatia" }, { "title": "SK/CZ" }, { "title": "Previews" }, { "title": "Rozhovory" }, { "title": "PR články" } ] }, { "id": 3, "categories": [ { "title": "Ešport" }, { "title": "Hry" }, { "title": "Tech" }, { "title": "Iné" } ], "subcategories": [ { "title": "Zahraničie" }, { "title": "Súťaže" } ] } ] } } } ```

As you can see, first and second articles have empty categories, while fetching them individually returns full data.

Workaround

My current workaround is to fetch categories and subcategories separately, same idea has been applied to the Project entity as well:

Very unpleasant workaround ```typescript const [totalCount, articles] = await prisma.$transaction([ prisma.article.count({ where }), prisma.article.findMany({ ...getPrismaPaginationFields(filter), where, orderBy: { publishedAt: "desc", }, include: { author: true, }, }), ]); /** * Fetching categories and subcategories separately due to a bug in prisma * @see https://github.com/prisma/prisma/issues/7713 */ const data = await Promise.all( articles.map(async (article) => ({ ...article, categories: await prisma.articleCategory.findMany({ where: { articles: { some: { id: article.id, }, }, }, }), subcategories: await prisma.articleSubcategory.findMany({ where: { articles: { some: { id: article.id, }, }, }, }), })) ); return { totalCount, data }; ```
nizamiza commented 2 years ago

I've updated prisma to version 3.3.0. Issue still persists.

nizamiza commented 2 years ago

Issue still persists on version 3.5.0.

nizamiza commented 2 years ago

Resolved in 3.7.0