prisma: Cascade deletes doesn't work on many to many relations
Bug description
I’m getting an error when I try to remove an entity from a explicit many-to-many relationship.
The change you are trying to make would violate the required relation 'ContactToServerToServer' between the
ContactToServerand
Server models.
How to reproduce
I am using an empty posgresql database and Typescript so:
- npx prisma generate
- npx prisma migrate save --name init --experimental
- npx prisma migrate up --experimental
- DEBUG=* npx ts-node index.ts
Expected behavior
I wanted to delete the server, ( and doing so, delete also the entry in the relation table ) The created contact should not be affected
Prisma information
My prisma schema:
`// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Contact {
id Int @default(autoincrement()) @id
name String @unique
servers ContactToServer[]
}
model ContactToServer {
contact Contact @relation(fields: [contactId], references: [id])
contactId Int
server Server @relation(fields: [serverId], references: [id])
serverId Int
functionality String
@@id([contactId,serverId])
}
model Server {
id Int @default(autoincrement()) @id
hostname String @unique
contacts ContactToServer[]
}`
and my Prisma queries:
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient({
log : ["info","query","warn"]
})
async function main() {
await test()
}
async function test(){
const contact = await prisma.contact.create({
data : {
name : "Alice"
}
})
const contact2 = await prisma.contact.create({
data : {
name : "Bob"
}
})
const server = await prisma.server.create({
data : {
hostname : "SERVER_01",
contacts : {
create : [
{
functionality : "Support",
contact : {
connect : {
name : "Bob"
}
}
},
{
functionality : "Hosting",
contact : {
connect : {
name : "Alice"
}
}
}
]
}
}
})
const deleteServer = await prisma.server.delete({
where : {
id : 1
}
})
return
}
main()
.catch(e => {
throw e
})
Environment & setup
- OS: Ubuntu 18.04.4 LTS
- Database: PostgreSQL 11.7
- Prisma version: prisma/1.34.10 (linux-x64)
- Node.js version: v12.14.1
- NPM version: 6.14.4
Stack Trace
prisma-client {
prisma-client engineConfig: {
prisma-client cwd: '/home/docker/Prisma/prisma',
prisma-client debug: false,
prisma-client datamodelPath: '/home/docker/Prisma/node_modules/@prisma/client/schema.prisma',
prisma-client prismaPath: undefined,
prisma-client datasources: [],
prisma-client generator: {
prisma-client name: 'client',
prisma-client provider: 'prisma-client-js',
prisma-client output: '/home/docker/Prisma/node_modules/@prisma/client',
prisma-client binaryTargets: [],
prisma-client config: {}
prisma-client },
prisma-client showColors: false,
prisma-client logLevel: 'info',
prisma-client logQueries: true,
prisma-client env: {
prisma-client DATABASE_URL: 'postgresql://test:test@localhost:5432/test2?schema=public'
prisma-client },
prisma-client flags: []
prisma-client }
prisma-client } +0ms
prisma-client Prisma Client call: +67ms
prisma-client prisma.contact.create({
prisma-client data: {
prisma-client name: 'Alice'
prisma-client }
prisma-client }) +1ms
prisma-client Generated request: +0ms
prisma-client mutation {
prisma-client createOneContact(data: {
prisma-client name: "Alice"
prisma-client }) {
prisma-client id
prisma-client name
prisma-client }
prisma-client }
prisma-client +0ms
engine {
engine PRISMA_DML_PATH: '/home/docker/Prisma/node_modules/@prisma/client/schema.prisma',
engine PORT: '39629',
engine RUST_BACKTRACE: '1',
engine RUST_LOG: 'info',
engine LOG_QUERIES: 'true',
engine OVERWRITE_DATASOURCES: '[]'
engine } +0ms
engine { cwd: '/home/docker/Prisma/prisma' } +0ms
getos { version: 'OpenSSL 1.1.1 11 Sep 2018\n' } +0ms
getos { ls: '' } +0ms
getos { platform: 'linux', libssl: '1.1.x' } +2ms
plusX Execution permissions of /home/docker/Prisma/node_modules/@prisma/client/runtime/query-engine-debian-openssl-1.1.x are fine +0ms
engine { flags: [ '--enable-raw-queries' ] } +4ms
engine stderr Printing to stderr for debugging +15ms
engine stderr Listening on 127.0.0.1:39629 +1ms
engine stdout {
timestamp: 'Apr 29 21:46:40.768',
level: 'INFO',
target: 'quaint::pooled',
fields: { message: 'Starting a postgresql pool with 15 connections.' }
} +0ms
prisma:info Starting a postgresql pool with 15 connections.
engine stdout {
timestamp: 'Apr 29 21:46:40.771',
level: 'INFO',
target: 'query_engine::server',
fields: { message: 'Started http server on 127.0.0.1:39629' }
} +3ms
prisma:info Started http server on 127.0.0.1:39629
engine stdout {
timestamp: 'Apr 29 21:46:40.789',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: { query: 'BEGIN', item_type: 'query', params: '[]', duration_ms: 0 }
} +18ms
prisma:query BEGIN
engine stdout {
timestamp: 'Apr 29 21:46:40.790',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: {
query: 'INSERT INTO "public"."Contact" ("name") VALUES ($1) RETURNING "public"."Contact"."id"',
item_type: 'query',
params: '["Alice"]',
duration_ms: 0
}
} +1ms
prisma:query INSERT INTO "public"."Contact" ("name") VALUES ($1) RETURNING "public"."Contact"."id"
engine stdout {
timestamp: 'Apr 29 21:46:40.791',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: {
query: 'SELECT "public"."Contact"."id", "public"."Contact"."name" FROM "public"."Contact" WHERE "public"."Contact"."id" = $1 LIMIT $2 OFFSET $3',
item_type: 'query',
params: '[1,1,0]',
duration_ms: 0
}
} +1ms
prisma:query SELECT "public"."Contact"."id", "public"."Contact"."name" FROM "public"."Contact" WHERE "public"."Contact"."id" = $1 LIMIT $2 OFFSET $3
engine stdout {
timestamp: 'Apr 29 21:46:40.802',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: {
query: 'COMMIT',
item_type: 'query',
params: '[]',
duration_ms: 10
}
} +11ms
prisma:query COMMIT
prisma-client Prisma Client call: +69ms
prisma-client prisma.contact.create({
prisma-client data: {
prisma-client name: 'Bob'
prisma-client }
prisma-client }) +0ms
prisma-client Generated request: +0ms
prisma-client mutation {
prisma-client createOneContact(data: {
prisma-client name: "Bob"
prisma-client }) {
prisma-client id
prisma-client name
prisma-client }
prisma-client }
prisma-client +0ms
engine stdout {
timestamp: 'Apr 29 21:46:40.817',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: { query: 'BEGIN', item_type: 'query', params: '[]', duration_ms: 0 }
} +16ms
prisma:query BEGIN
engine stdout {
timestamp: 'Apr 29 21:46:40.818',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: {
query: 'INSERT INTO "public"."Contact" ("name") VALUES ($1) RETURNING "public"."Contact"."id"',
item_type: 'query',
params: '["Bob"]',
duration_ms: 0
}
} +1ms
prisma:query INSERT INTO "public"."Contact" ("name") VALUES ($1) RETURNING "public"."Contact"."id"
engine stdout {
timestamp: 'Apr 29 21:46:40.819',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: {
query: 'SELECT "public"."Contact"."id", "public"."Contact"."name" FROM "public"."Contact" WHERE "public"."Contact"."id" = $1 LIMIT $2 OFFSET $3',
item_type: 'query',
params: '[2,1,0]',
duration_ms: 0
}
} +1ms
prisma:query SELECT "public"."Contact"."id", "public"."Contact"."name" FROM "public"."Contact" WHERE "public"."Contact"."id" = $1 LIMIT $2 OFFSET $3
engine stdout {
timestamp: 'Apr 29 21:46:40.821',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: { query: 'COMMIT', item_type: 'query', params: '[]', duration_ms: 1 }
} +1ms
prisma:query COMMIT
prisma-client Prisma Client call: +10ms
prisma-client prisma.server.create({
prisma-client data: {
prisma-client hostname: 'SERVER_01',
prisma-client contacts: {
prisma-client create: [
prisma-client {
prisma-client functionality: 'Support',
prisma-client contact: {
prisma-client connect: {
prisma-client name: 'Bob'
prisma-client }
prisma-client }
prisma-client },
prisma-client {
prisma-client functionality: 'Hosting',
prisma-client contact: {
prisma-client connect: {
prisma-client name: 'Alice'
prisma-client }
prisma-client }
prisma-client }
prisma-client ]
prisma-client }
prisma-client }
prisma-client }) +0ms
prisma-client Generated request: +0ms
prisma-client mutation {
prisma-client createOneServer(data: {
prisma-client hostname: "SERVER_01"
prisma-client contacts: {
prisma-client create: [
prisma-client {
prisma-client functionality: "Support"
prisma-client contact: {
prisma-client connect: {
prisma-client name: "Bob"
prisma-client }
prisma-client }
prisma-client },
prisma-client {
prisma-client functionality: "Hosting"
prisma-client contact: {
prisma-client connect: {
prisma-client name: "Alice"
prisma-client }
prisma-client }
prisma-client }
prisma-client ]
prisma-client }
prisma-client }) {
prisma-client id
prisma-client hostname
prisma-client }
prisma-client }
prisma-client +0ms
engine stdout {
timestamp: 'Apr 29 21:46:40.826',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: { query: 'BEGIN', item_type: 'query', params: '[]', duration_ms: 0 }
} +6ms
prisma:query BEGIN
engine stdout {
timestamp: 'Apr 29 21:46:40.827',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: {
query: 'INSERT INTO "public"."Server" ("hostname") VALUES ($1) RETURNING "public"."Server"."id"',
item_type: 'query',
params: '["SERVER_01"]',
duration_ms: 0
}
} +1ms
prisma:query INSERT INTO "public"."Server" ("hostname") VALUES ($1) RETURNING "public"."Server"."id"
engine stdout {
timestamp: 'Apr 29 21:46:40.828',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: {
query: 'SELECT "public"."Contact"."id" FROM "public"."Contact" WHERE "public"."Contact"."name" = $1 OFFSET $2',
item_type: 'query',
params: '["Bob",0]',
duration_ms: 0
}
} +0ms
prisma:query SELECT "public"."Contact"."id" FROM "public"."Contact" WHERE "public"."Contact"."name" = $1 OFFSET $2
engine stdout {
timestamp: 'Apr 29 21:46:40.829',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: {
query: 'INSERT INTO "public"."ContactToServer" ("contactId","serverId","functionality") VALUES ($1,$2,$3) RETURNING "public"."ContactToServer"."contactId", "public"."ContactToServer"."serverId"',
item_type: 'query',
params: '[2,1,"Support"]',
duration_ms: 1
}
} +2ms
prisma:query INSERT INTO "public"."ContactToServer" ("contactId","serverId","functionality") VALUES ($1,$2,$3) RETURNING "public"."ContactToServer"."contactId", "public"."ContactToServer"."serverId"
engine stdout {
timestamp: 'Apr 29 21:46:40.830',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: {
query: 'SELECT "public"."Contact"."id" FROM "public"."Contact" WHERE "public"."Contact"."name" = $1 OFFSET $2',
item_type: 'query',
params: '["Alice",0]',
duration_ms: 0
}
} +0ms
prisma:query SELECT "public"."Contact"."id" FROM "public"."Contact" WHERE "public"."Contact"."name" = $1 OFFSET $2
engine stdout {
timestamp: 'Apr 29 21:46:40.831',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: {
query: 'INSERT INTO "public"."ContactToServer" ("contactId","serverId","functionality") VALUES ($1,$2,$3) RETURNING "public"."ContactToServer"."contactId", "public"."ContactToServer"."serverId"',
item_type: 'query',
params: '[1,1,"Hosting"]',
duration_ms: 0
}
} +3ms
prisma:query INSERT INTO "public"."ContactToServer" ("contactId","serverId","functionality") VALUES ($1,$2,$3) RETURNING "public"."ContactToServer"."contactId", "public"."ContactToServer"."serverId"
engine stdout {
timestamp: 'Apr 29 21:46:40.835',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: {
query: 'SELECT "public"."Server"."id", "public"."Server"."hostname" FROM "public"."Server" WHERE "public"."Server"."id" = $1 LIMIT $2 OFFSET $3',
item_type: 'query',
params: '[1,1,0]',
duration_ms: 3
}
} +2ms
prisma:query SELECT "public"."Server"."id", "public"."Server"."hostname" FROM "public"."Server" WHERE "public"."Server"."id" = $1 LIMIT $2 OFFSET $3
engine stdout {
timestamp: 'Apr 29 21:46:40.836',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: { query: 'COMMIT', item_type: 'query', params: '[]', duration_ms: 1 }
} +2ms
prisma:query COMMIT
prisma-client Prisma Client call: +14ms
prisma-client prisma.server.delete({
prisma-client where: {
prisma-client id: 1
prisma-client }
prisma-client }) +0ms
prisma-client Generated request: +0ms
prisma-client mutation {
prisma-client deleteOneServer(where: {
prisma-client id: 1
prisma-client }) {
prisma-client id
prisma-client hostname
prisma-client }
prisma-client }
prisma-client +0ms
engine stdout {
timestamp: 'Apr 29 21:46:40.839',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: { query: 'BEGIN', item_type: 'query', params: '[]', duration_ms: 0 }
} +3ms
prisma:query BEGIN
engine stdout {
timestamp: 'Apr 29 21:46:40.840',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: {
query: 'SELECT "public"."Server"."id", "public"."Server"."hostname" FROM "public"."Server" WHERE "public"."Server"."id" = $1 LIMIT $2 OFFSET $3',
item_type: 'query',
params: '[1,1,0]',
duration_ms: 0
}
} +0ms
prisma:query SELECT "public"."Server"."id", "public"."Server"."hostname" FROM "public"."Server" WHERE "public"."Server"."id" = $1 LIMIT $2 OFFSET $3
engine stdout {
timestamp: 'Apr 29 21:46:40.841',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: {
query: 'SELECT "public"."ContactToServer"."contactId", "public"."ContactToServer"."serverId", "public"."ContactToServer"."serverId" FROM "public"."ContactToServer" WHERE (1=1 AND "public"."ContactToServer"."serverId" IN ($1)) OFFSET $2',
item_type: 'query',
params: '[1,0]',
duration_ms: 0
}
} +1ms
prisma:query SELECT "public"."ContactToServer"."contactId", "public"."ContactToServer"."serverId", "public"."ContactToServer"."serverId" FROM "public"."ContactToServer" WHERE (1=1 AND "public"."ContactToServer"."serverId" IN ($1)) OFFSET $2
engine stdout {
timestamp: 'Apr 29 21:46:40.841',
level: 'INFO',
target: 'quaint::connector::metrics',
fields: {
query: 'ROLLBACK',
item_type: 'query',
params: '[]',
duration_ms: 0
}
} +0ms
prisma:query ROLLBACK
engine {
engine error: PrismaClientKnownRequestError: The change you are trying to make would violate the required relation 'ContactToServerToServer' between the `ContactToServer` and `Server` models.
engine at NodeEngine.graphQLToJSError (/home/docker/Prisma/node_modules/@prisma/engine-core/dist/NodeEngine.js:560:1)
engine at /home/docker/Prisma/node_modules/@prisma/engine-core/dist/NodeEngine.js:468:1
engine at process.result (internal/process/task_queues.js:94:5)
engine at PrismaClientFetcher.request (/home/docker/Prisma/node_modules/@prisma/client/src/runtime/getPrismaClient.ts:621:13) {
engine code: 'P2014',
engine meta: {
engine relation_name: 'ContactToServerToServer',
engine model_a_name: 'ContactToServer',
engine model_b_name: 'Server'
engine }
engine }
engine } +1ms
printStack callsite Error:
at Object.model [as Server] (/home/docker/Prisma/node_modules/@prisma/client/src/runtime/getPrismaClient.ts:377:28)
at Object.n.<computed> [as delete] (/home/docker/Prisma/node_modules/@prisma/client/src/runtime/getPrismaClient.ts:512:33)
at /home/docker/Prisma/index.ts:49:52
at step (/home/docker/Prisma/index.ts:33:23)
at Object.next (/home/docker/Prisma/index.ts:14:53)
at fulfilled (/home/docker/Prisma/index.ts:5:58)
at processTicksAndRejections (internal/process/task_queues.js:94:5) +0ms
(node:26476) UnhandledPromiseRejectionWarning: Error:
Invalid `prisma.server.delete()` invocation in
/home/docker/Prisma/index.ts:49:52
The change you are trying to make would violate the required relation 'ContactToServerToServer' between the `ContactToServer` and `Server` models.
at PrismaClientFetcher.request (/home/docker/Prisma/node_modules/@prisma/client/src/runtime/getPrismaClient.ts:640:15)
at processTicksAndRejections (internal/process/task_queues.js:94:5)
(node:26476) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:26476) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 24
- Comments: 31 (7 by maintainers)
We are actively working on this. This week the schema working group made their decision on how it should look like in the data model. The next and final step is the implementation.
Any update on this? This is a very high priority bug which can make or break almost any project.
If you want to delete nested records in cascade deleting the parent, before you need set constraint key as nullable in the database or in your schema. This solve the issue.
I am also having this issue. More than 5 months since this has been opened an there is still no fix? What’s the deal?
Cascade delete currently doesn’t work for me for 1:n relations either.
The change you are trying to make would violate the required relation 'AToB' between the 'A' and 'B' models.
prisma (2.19.0)Fixed in https://github.com/prisma/prisma-engines/pull/1947 Proposal for you all to read in https://github.com/prisma/prisma/issues/6996
Instructions on how to use this, and a place to give feedback: https://github.com/prisma/prisma/issues/7816
Our project heavily depends on cascade deletions. After weeks of work to migrate from Typeorm to Prisma, we faced with this issue. This is big, Prisma should’ve mention this shortcoming in limitations so we would wait until it fixed. This not some behavior you would expect from a “production-ready” software!
This has now been released as a preview feature flag. You can read about it in the release notes for 2.26.0: https://github.com/prisma/prisma/releases/tag/2.26.0 If you have any feedback, please use this issue: https://github.com/prisma/prisma/issues/7816
What is the status with this? What is the solution? Has been almost a year since this was opened
I’m also facing this issue.
When deleting directly in the db (or by using tools like pgAdmin), N:M relation is correctly deleted, the prisma client prevents performing this delete operation.
This issue should be renamed to: Support cascade delete in Many-to-Many relationships and be tagged with topic:cascade.
You will have this error again until it is fixed.
Straight to the point, I have an API endpoint which will call to prisma.ENTITY.deleteMany. If I do just that, the bug we are talking about will be thrown. To “fix” this, I added a call to prisma.executeRaw and deleting the things I want deleted in the database “manually” Documentation here
so what is the solution when you face this? this is not a minor bug! what can i do to go forward with my project? if i reinitalize prisma from the start, will i face this issue again?
Now I can only look at database relationships and delete them using $queryraw.
@JunquerGJ Thanks, i think this is what i’m going to do for now, otherwise the project i’m working on is frozen because of this bug. I hope they prioritize this issue.
This issue is opened by almost one year. How is it possible that with all the huge work that the prisma team is doing to improve the library, no solution has been provided yet? This is quite a big issue, and I think that it should also be labeled as bug.
This is actually a bug as it should not be needed for cascade, see https://github.com/prisma/prisma/issues/4650
This issue seems to also affect
disconnect
andset
fields in CRUD mutations.Querying my data to show initial state
Query where
$id = doc1
:Result:
Now I want to disconnect the
documentKind
withid = cuid2
.Mutation
Attempt 1 with "disconnect"
Variables:
Result:
Attempt 2 with "set"
Variables:
Result: Same as above.
Note that I’m using
nexus-plugin-prisma
. Let me know if I should open a new issue (here or there).