Skip to content

Compound primary key + abstract models issue #1129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Liam-Scott-Russell opened this issue Mar 13, 2024 · 3 comments
Closed

Compound primary key + abstract models issue #1129

Liam-Scott-Russell opened this issue Mar 13, 2024 · 3 comments
Milestone

Comments

@Liam-Scott-Russell
Copy link

Description

I cannot create an instance of a model when it has a compound primary key that is sourced from two abstract models. At runtime it fails to generate a valid select key for the create call, and then Prisma errors.

schema.zmodel

plugin prisma {
  provider = '@core/prisma'
  output = './schema.prisma'
  format = true
}

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["metrics", "tracing", "views", "relationJoins"]
}

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

model Relation1 {
  id String @db.Uuid @id @default(dbgenerated("gen_random_uuid()"))
  field1 String
  concrete Concrete[]
  @@allow('all', true)
}

model Relation2 {
  id String @db.Uuid @id @default(dbgenerated("gen_random_uuid()"))
  field2 String
  concrete Concrete[]
  @@allow('all', true)
}

abstract model WithRelation1 {
  relation1Id String @db.Uuid
  relation1 Relation1 @relation(fields: [relation1Id], references: [id])
}
abstract model WithRelation2 {
  relation2Id String @db.Uuid
  relation2 Relation2 @relation(fields: [relation2Id], references: [id])
}

model Concrete extends WithRelation1, WithRelation2 {
  concreteField String
  @@id([relation1Id, relation2Id])
  @@allow('all', true)
}

Script

import { PrismaClient } from '@prisma/client';
import { enhance } from '@zenstackhq/runtime';

const prisma = new PrismaClient({
  log: ['info'],
});

const db = enhance(prisma, {}, { logPrismaQuery: true });

db.$transaction(async (tx) => {
  await tx.relation2.createMany({
    data: [
      {
        field2: 'field2Value1',
      },
      {
        field2: 'field2Value2',
      },
    ],
  }),
    await tx.relation1.create({
      data: {
        field1: 'field1Value',
        concrete: {
          createMany: {
            data: [
              {
                concreteField: 'concreteFieldValue1',
                relation2Id: 'relation2Id1',
              },
              {
                concreteField: 'concreteFieldValue2',
                relation2Id: 'relation2Id2',
              },
            ],
          },
        },
      },
    });
}).then(console.log, console.error);

Logs

prisma:info Starting a postgresql pool with 17 connections.
prisma:info [policy] `create` relation1: {
  data: {
    field1: 'field1Value',
    concrete: {
      createMany: {
        data: [
          {
            concreteField: 'concreteFieldValue1',
            relation2Id: 'relation2Id1'
          },
          {
            concreteField: 'concreteFieldValue2',
            relation2Id: 'relation2Id2'
          }
        ]
      }
    }
  },
  select: { id: true, concrete: { select: {} } }
}
Error calling enhanced Prisma method `create`:
Invalid `prisma.relation1.create()` invocation:

{
  data: {
    field1: "field1Value",
    concrete: {
      createMany: {
        data: [
          {
            concreteField: "concreteFieldValue1",
            relation2Id: "relation2Id1"
          },
          {
            concreteField: "concreteFieldValue2",
            relation2Id: "relation2Id2"
          }
        ]
      }
    }
  },
  select: {
    id: true,
    concrete: {
      select: {
?       relation1Id?: true,
?       relation2Id?: true,
?       concreteField?: true,
?       relation1?: true,
?       relation2?: true
      }
    }
  }
}

The `select` statement for type Concrete must not be empty. Available options are marked with ?.
    at <anonymous> (<src>/repro.ts:21:24),
    at async Proxy._transactionWithCallback (<node_modules>/@prisma/client/runtime/library.js:128:9534) {
  name: 'PrismaClientValidationError',
  clientVersion: '5.10.2',
  internalStack: 'PrismaClientValidationError: \n' +
    'Invalid `prisma.relation1.create()` invocation:\n' +
    '\n' +
    '{\n' +
    '  data: {\n' +
    '    field1: "field1Value",\n' +
    '    concrete: {\n' +
    '      createMany: {\n' +
    '        data: [\n' +
    '          {\n' +
    '            concreteField: "concreteFieldValue1",\n' +
    '            relation2Id: "relation2Id1"\n' +
    '          },\n' +
    '          {\n' +
    '            concreteField: "concreteFieldValue2",\n' +
    '            relation2Id: "relation2Id2"\n' +
    '          }\n' +
    '        ]\n' +
    '      }\n' +
    '    }\n' +
    '  },\n' +
    '  select: {\n' +
    '    id: true,\n' +
    '    concrete: {\n' +
    '      select: {\n' +
    '?       relation1Id?: true,\n' +
    '?       relation2Id?: true,\n' +
    '?       concreteField?: true,\n' +
    '?       relation1?: true,\n' +
    '?       relation2?: true\n' +
    '      }\n' +
    '    }\n' +
    '  }\n' +
    '}\n' +
    '\n' +
    'The `select` statement for type Concrete must not be empty. Available options are marked with ?.\n' +
    '    at Cn (<node_modules>/@prisma/client/runtime/library.js:116:5888)\n' +
    '    at _n.handleRequestError (<node_modules>/@prisma/client/runtime/library.js:123:6510)\n' +
    '    at _n.handleAndLogRequestError (<node_modules>/@prisma/client/runtime/library.js:123:6188)\n' +
    '    at _n.request (<node_modules>/library.js:123:5896)\n' +
    '    at async l (<node_modules>/@prisma/client/runtime/library.js:128:10871)'

Expected

It should be sending:

relation1: {
  data: {
    field1: 'field1Value',
    concrete: {
      createMany: {
        data: [
          {
            concreteField: 'concreteFieldValue1',
            relation2Id: 'relation2Id1'
          },
          {
            concreteField: 'concreteFieldValue2',
            relation2Id: 'relation2Id2'
          }
        ]
      }
    }
  },
  select: { id: true, concrete: { select: { relation1Id: true, relation2Id: true } } }
}

Ideas

Computed ID Fields

I think that the error comes from these lines, specifically when generating the IDs for each model.

These are the ID fields from here

Relation1

[{"name":"id","type":"String","isId":true,"attributes":[{"name":"@db.Uuid","args":[]},{"name":"@id","args":[]},{"name":"@default","args":[]}]}]

ConcreteModel

[]

For some reason, there are no IDs here.

Environment

  • ZenStack version: zenstack 1.10.3 @zenstackhq/runtime 1.10.3 @zenstackhq/sdk 1.10.3
  • Prisma version: prisma 5.10.2 @prisma/client 5.8.1
  • Database type: Postgresql
@Liam-Scott-Russell
Copy link
Author

This is definitely an issue with the abstract model inheritance, as if I don't use that and have the following schema, it works:

schema.zmodel

plugin prisma {
  provider = '@core/prisma'
  output = './schema.prisma'
  format = true
}

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["metrics", "tracing", "views", "relationJoins"]
}

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

model Relation1 {
  id String @db.Uuid @id @default(dbgenerated("gen_random_uuid()"))
  field1 String
  concrete Concrete[]
  @@allow('all', true)
}

model Relation2 {
  id String @db.Uuid @id @default(dbgenerated("gen_random_uuid()"))
  field2 String
  concrete Concrete[]
  @@allow('all', true)
}

model Concrete {
  concreteField String
  relation1Id String @db.Uuid
  relation1 Relation1 @relation(fields: [relation1Id], references: [id])
  relation2Id String @db.Uuid
  relation2 Relation2 @relation(fields: [relation2Id], references: [id])
  @@id([relation1Id, relation2Id])
  @@allow('all', true)
}

Computed IDs

[{"name":"relation1Id","type":"String","isId":true,"attributes":[{"name":"@db.Uuid","args":[]}],"isForeignKey":true},{"name":"relation2Id","type":"String","isId":true,"attributes":[{"name":"@db.Uuid","args":[]}],"isForeignKey":true}]

This isn't ideal, as I use the WithXYZ pattern extensively to declutter and DRY up common relations (e.g. a withAuthor abstract model with appropriate auth).

@ymc9
Copy link
Member

ymc9 commented Mar 13, 2024

Thanks for reporting this @Liam-Scott-Russell ! I'll look into it and let you know my findings.

@ymc9
Copy link
Member

ymc9 commented Mar 13, 2024

Confirmed there's an issue with recognizing id fields inherited from an abstract base. I'm making a fix and it'll be contained in the upcoming v1.11.0. Thank you for catching this!

@ymc9 ymc9 added this to the v1.11.0 milestone Mar 13, 2024
@ymc9 ymc9 closed this as completed Mar 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants