MichalLytek / typegraphql-prisma

Prisma generator to emit TypeGraphQL types and CRUD resolvers from your Prisma schema

Home Page:https://prisma.typegraphql.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

FindMany nested object queries returning NULL

spencerbull opened this issue · comments

Describe the Bug
FindMany nested object queries are returning NULL for the Many-to-One query use case. For the example below, I have a database in a star schema with fact and dimension tables. The FindMany queries return valid children when making queries in a One-to-Many direction, but return NULL in a Many-to-one Direction.

To Reproduce
Using typegraphql generator with basic configuration and simplified data model

generator typegraphql {
    provider = "typegraphql-prisma"
}

model DimJobRole {
    JobRoleId                 BigInt                         @default(autoincrement())
    JobFunctionName    String                         @db.VarChar(128)
    JobRoleEvents          FactJobRoleEvent[]
}

model FactJobRoleEvent {
    JobRoleEventId    BigInt                              @id(map: "PK__1832492348")
    JobRoleId             BigInt                              
    DimJobRole          DimJobRole                    @relation(fields: [JobRoleId], references: [JobRoleId], onUpdate: NoAction, map: "FK_FactJobRoleEvents_DimJobRole"
}

Example query FindMany queries are returning null.

query FactJobRoleEvents($take: Int) {
  factJobRoleEvent(take: $take) {
    jobRoleEventId
    jobRoleEventName
    DimJobRole {
      JobFunctionName
    }
  }
}

This query results in an error since DimJobRole cannot be null. I did confirm by making the following generated relations resolver nullable set to true. If the take parameter is set to 1, then the query resolves, if the query is set to >1, then the query returns null for all children.

Attached below in the logs section are the Prisma queries that are being run with verbose debug logs.

Expected Behavior
When making a GraphQL findMany query in the direction of Many-to-One, the children should return valid objects corresponding the to the table.

Logs
For take set to 1

prisma:client:libraryEngine  sending request, this.libraryStarted: true +45ms
prisma:query SELECT ....(full table).... WHERE ([dbo].[FactJobRoleEvent].[JobRoleEventId] = @P1 AND 1=1) ORDER BY 1 OFFSET @P2 ROWS FETCH NEXT @P3 ROWS ONLY
prisma:query SELECT [dbo].[DimJobRole].[JobRoleId] FROM [dbo].[DimJobRole] WHERE [dbo].[DimJobRole].[JobRoleId] IN (@P1)

For take set to 2

prisma:query SELECT ....(full table) WHERE 1=1 ORDER BY [dbo].[FactJobRoleEvnet].[JobRoleEventId] ASC OFFSET @P1 ROWS FETCH NEXT @P2 ROWS ONLY
prisma:query SELECT ....(query fields) .... FROM [dbo].[FactJobRoleEvnet] WHERE [dbo].[FactJobRoleEvnet].[JobRoleEventId] IN (@P1,@P2)
prisma:query SELECT ....(full table).... WHERE [dbo].[DimJobRole].[JobRoleId] IN (@P1,@P2)

Environment:

  • OS: [e.g. Windows 10 22H2]
  • Node (e.g. v18.15.0)
  • typegraphql-prisma version 0.24.2
  • Prisma version 4.11.0
  • TypeScript version 4.9.5

** Aditional Context **

Possibly similar to #248

I had a quick update I wanted to provide. In my relationResolvers. I found that replacing the following findUnique to findUniqueOrThrow is now returning values. For a temporary fix, I'm updating the generated code.

Example of the change...

Generated Relation Resolvers

@TypeGraphQL.FieldResolver(_type => DimJobRole, {
    nullable: false
  })
  async DimJobRole(@TypeGraphQL.Root() factJobRoleEvent: FactJobRoleEvent, @TypeGraphQL.Ctx() ctx: any, @TypeGraphQL.Info() info: GraphQLResolveInfo): Promise<DimJobRole> {
    const { _count } = transformInfoIntoPrismaArgs(info);
    return getPrismaFromContext(ctx).factJobRoleEvent.findUnique({
      where: {
        FactJobEventId: factJobRoleEvent.jobRoleEventId,
      },
    }).DimCalendar({
      ...(_count && transformCountFieldIntoSelectRelationsCount(_count)),
    });
  }

Updated Relation Resolvers

@TypeGraphQL.FieldResolver(_type => DimJobRole, {
    nullable: false
  })
  async DimJobRole(@TypeGraphQL.Root() factJobRoleEvent: FactJobRoleEvent, @TypeGraphQL.Ctx() ctx: any, @TypeGraphQL.Info() info: GraphQLResolveInfo): Promise<DimJobRole> {
    const { _count } = transformInfoIntoPrismaArgs(info);
    return getPrismaFromContext(ctx).factJobRoleEvent.findUniqueOrThrow({
      where: {
        FactJobEventId: factJobRoleEvent.jobRoleEventId,
      },
    }).DimCalendar({
      ...(_count && transformCountFieldIntoSelectRelationsCount(_count)),
    });
  }

I would recommend not marking this issue as fixed, but I did want to highlight a path forward here with an update.

I am able to reproduce the issue 👍

However, after digging a bit I think you've discovered an issue in Prisma Client built-in data-loader 😄

Let's reproduce the issue:

Schema:

model DimJobRole {
  JobRoleId       BigInt             @id @default(autoincrement())
  JobFunctionName String             @db.VarChar(128)
  JobRoleEvents   FactJobRoleEvent[]
}

model FactJobRoleEvent {
  JobRoleEventId BigInt     @id(map: "PK__1832492348")
  JobRoleId      BigInt
  DimJobRole     DimJobRole @relation(fields: [JobRoleId], references: [JobRoleId], onUpdate: NoAction, map: "FK_FactJobRoleEvents_DimJobRole")
}

Seed:

const [job1, job2, job3] = await Promise.all([
    prisma.dimJobRole.create({
      data: { JobFunctionName: "JobFunctionName 1" },
    }),
    prisma.dimJobRole.create({
      data: { JobFunctionName: "JobFunctionName 2" },
    }),
    prisma.dimJobRole.create({
      data: { JobFunctionName: "JobFunctionName 3" },
    }),
  ]);

  await Promise.all([
    prisma.factJobRoleEvent.create({
      data: {
        JobRoleEventId: 1,
        DimJobRole: {
          connect: { JobRoleId: job1.JobRoleId },
        },
      },
    }),
    prisma.factJobRoleEvent.create({
      data: {
        JobRoleEventId: 2,
        DimJobRole: {
          connect: { JobRoleId: job1.JobRoleId },
        },
      },
    }),
    prisma.factJobRoleEvent.create({
      data: {
        JobRoleEventId: 3,
        DimJobRole: {
          connect: { JobRoleId: job2.JobRoleId },
        },
      },
    }),
  ]);

Script 1 - await one by one:

const factJobRoleEvents = await prisma.factJobRoleEvent.findMany({
    take: 3,
  });
  console.log(factJobRoleEvents);
  for (const factJobRoleEvent of factJobRoleEvents) {
    const dimJobRole = await prisma.factJobRoleEvent
      .findUnique({
        where: {
          JobRoleEventId: factJobRoleEvent.JobRoleEventId,
        },
      })
      .DimJobRole({});
    console.log({dimJobRole});
  }

Logs:

[
  { JobRoleEventId: 1n, JobRoleId: 23n },
  { JobRoleEventId: 2n, JobRoleId: 23n },
  { JobRoleEventId: 3n, JobRoleId: 22n }
]
{
  dimJobRole: { JobRoleId: 23n, JobFunctionName: 'JobFunctionName 1' }
}
{
  dimJobRole: { JobRoleId: 23n, JobFunctionName: 'JobFunctionName 1' }
}
{
  dimJobRole: { JobRoleId: 22n, JobFunctionName: 'JobFunctionName 2' }
}

Script 2 - await Promise.all (like paralel field resolvers execution):

const factJobRoleEvents = await prisma.factJobRoleEvent.findMany({
    take: 3,
  });
  console.log(factJobRoleEvents);
 
  await Promise.all(
    factJobRoleEvents.map(async factJobRoleEvent => {
      const dimJobRole = await prisma.factJobRoleEvent
        .findUnique({
          where: {
            JobRoleEventId: factJobRoleEvent.JobRoleEventId,
          },
        })
        .DimJobRole({});
      console.log({ dimJobRole });
    }),
  );

Logs:

[
  { JobRoleEventId: 1n, JobRoleId: 19n },
  { JobRoleEventId: 2n, JobRoleId: 19n },
  { JobRoleEventId: 3n, JobRoleId: 21n }
]
{ dimJobRole: null }
{ dimJobRole: null }
{ dimJobRole: null }

Script 3 - promise all + OrThrow:

const factJobRoleEvents = await prisma.factJobRoleEvent.findMany({
    take: 3,
  });
  console.log(factJobRoleEvents);
  
await Promise.all(
    factJobRoleEvents.map(async factJobRoleEvent => {
      const dimJobRole = await prisma.factJobRoleEvent
        .findUniqueOrThrow({
          where: {
            JobRoleEventId: factJobRoleEvent.JobRoleEventId,
          },
        })
        .DimJobRole({});
      console.log({ dimJobRole });
    }),
  );

Logs:

[
  { JobRoleEventId: 1n, JobRoleId: 25n },
  { JobRoleEventId: 2n, JobRoleId: 25n },
  { JobRoleEventId: 3n, JobRoleId: 26n }
]
{
  dimJobRole: { JobRoleId: 25n, JobFunctionName: 'JobFunctionName 1' }
}
{
  dimJobRole: { JobRoleId: 25n, JobFunctionName: 'JobFunctionName 1' }
}
{
  dimJobRole: { JobRoleId: 26n, JobFunctionName: 'JobFunctionName 2' }
}

Reported in Prisma:
prisma/prisma#18326

But I will fix this issue by just using findUniqueOrThrow - it's a proper way anyway, as the record has to exist as it was returned from a parent db query 👀

Fixed in 3ea20f6 - @spencerbull let me know if the fix does not work for you 😉

This looks great thanks!