prisma: Postgres: Deleting a record that doesn't exist gives a bad error

Problem

The error we get back from bigserial ids it not very nice.

await prisma.types.delete({
  where: {
    bigserial: "non-existent id",
  },
})
PrismaClientKnownRequestError2: 
Invalid `prisma.types.delete()` invocation in
/Users/m/Go/src/github.com/prisma/qa-native-types/index.ts:72:30

   68 //   },
   69 // })
   70 
   71 console.log(
→  72   await prisma.types.delete(
  Query interpretation error. Error for binding '0': RecordNotFound("Record to delete does not exist.")
    at PrismaClientFetcher.request (/Users/m/Go/src/github.com/prisma/qa-native-types/node_modules/@prisma/client/runtime/index.js:15871:15)
    at processTicksAndRejections (internal/process/task_queues.js:97:5) {
  code: 'P2016',
  clientVersion: '2.10.0-dev.58',
  meta: {
    details: `Error for binding '0': RecordNotFound("Record to delete does not exist.")`
  }
}

Suggested Solution

Remove Query interpretation error. Error for binding '0':

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 50
  • Comments: 37 (4 by maintainers)

Commits related to this issue

Most upvoted comments

It is quite absurd this doesn’t exist yet even though the issue was opened 3 years ago…

I have this issue as well, deleteIfExists?

It would be great to have something akin to the following for delete():

await this.prisma.product.delete({
    where: {
      id: product.id,
    },
    rejectOnNotFound: false,
});

The current behavior makes things hard to deal with when trying to write idempotent functions for handling Stripe webhooks etc.

While using deleteMany() is a decent workaround it doesn’t work in cases such as the below:

await this.prisma.product.update({
  where: {
    id: product.id,
  },
  data: {
    price: {
        delete: true,
    }
  },
});

Something akin to the following would be handy as well:

await this.prisma.product.update({
  where: {
    id: product.id,
  },
  data: {
    price: {
        delete: {
           rejectOnNotFound: false,
        },
    }
  },
});

Any update? deleteMany - is NOT a workaround because it doesn’t work for 1 <-> 1 relations (works for 1 <-> N) try <-> catch - is NOT a workaround because it doesn’t work for nested delete

you can catch it until the delete if not exists is implemented is changed

await prisma.entity.delete({
  where: { id: product.id }
}).catch()

Facing a similar issue as well:

An operation failed because it depends on one or more records that were required but not found. 
Record to delete does not exist.

Right now I’m catching it in a try-catch but would love to be able to have a similar function as upsert but for deletes 😃

Same issue here as well. Feel like it shouldn’t require a different method, just handle more gracefully?

My work around for this was to change delete for deleteMany. Hope it helps

@mindlid Based on the above comment, I’m using it as follows.

await prisma.entity.delete({ where: { id } }).catch(() => {
  throw new NotFoundException(`Can't find item with id ${id}`);
});

Hi. I come from the future, and this issue is still open for the next 2 years.

Are there any workarounds available for one-to-one relationships?

#9460 doesn’t fully cover everything because deleteMany doesn’t work for 1 <-> 1 relations (unless the suggestion is to cover 1 <-> 1 and 1 <-> N and N <-> N with deleteIfExists). It also doesn’t matter if we know the error code, this try <-> catch doesn’t work if we do a nested delete.

If we could cover these cases (https://github.com/prisma/prisma/issues/4072#issuecomment-1557486529) without throwing an error it would be perfect. Ideally, when the record to delete doesn’t exist it should just ignore that and proceed further.

Are there any workarounds available for one-to-one relationships?

It ain’t pretty but works for me:

    this.prisma.user.delete({ where: { id: id } }).catch((e) => {
      if (e instanceof Prisma.PrismaClientKnownRequestError) {
        if (e.code === 'P2025' || e.code === 'P2016') {
          throw new NotFoundException(`Can't find user with id ${id}`);
        }
      }
      throw e;
    });

On my relation, I use onDelete: Cascade to also remove all relational registers.

If you are using nestjs, a better approach would be using an exception filter, you would not neet try/catching all around your code, just enable the filter globally. Check it out:

// main.ts
//(...)
  const { httpAdapter } = app.get(HttpAdapterHost);
  app.useGlobalFilters(new PrismaClientExceptionFilter(httpAdapter));
//(...)
Catch(Prisma.PrismaClientKnownRequestError)
export class PrismaClientExceptionFilter extends BaseExceptionFilter {
  catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) {
    console.error(exception.message);
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    // default prisma errors message
    // const message = exception.message.replace(/\n/g, '');

    switch (exception.code) {
      case 'P2025':
      case 'P2016':
        response.status(HttpStatus.NOT_FOUND).json({
          statusCode: HttpStatus.NOT_FOUND,
          message: "An operation failed because it depends on one or more records that were required but not found.",
        });
        break;
      
      default:
        // default 500 error code
        super.catch(exception, host);
        break;
    }
  }
}

references: https://www.prisma.io/docs/reference/api-reference/error-reference https://www.prisma.io/docs/concepts/components/prisma-schema/relations/referential-actions https://www.prisma.io/blog/nestjs-prisma-error-handling-7D056s1kOop2

I am encountering the same annoying issue, when updating a record and passing delete: { true }, for one of the relations.

I’m facing the same issue in this simple example, I don’t really care if it exists or not I just want to make sure its deleted before creating a new one. I think opting out so that delete doesn’t throw might be a good approach.

await db.$transaction([
                db.microsite.delete({
                    where: {
                        slug: micrositeSlug
                    }
                }),
                db.microsite.create({
                    data: {
                       ...
                    }
                })
            ]);

@mindlid Based on the above comment, I’m using it as follows.

await prisma.entity.delete({ where: { id } }).catch(() => {
  throw new NotFoundException(`Can't find item with id ${id}`);
});

Thank you so much! It works for me with prisma version 5.5.2

I would recommend against using this approach, as now any error, even a temporary network issue, will be treated the same as “the database answered that there is no record with id $id”…

Checking for P2025 or P2016 seems like a better interim solution…

We have the same happening to us on MariaDB with ints: Prisma version: 2.22.1

const nonExistingId = 1234;
this._prisma.someModel.delete({
  where: {
    someId: nonExistingId,
  },
})
driver_1  | InterpretationError("Error for binding \'0\'", Some(QueryGraphBuilderError(RecordNotFound("Record to delete does not exist.")))) +41845ms
driver_1  | Error: Error occurred during query execution:
driver_1  | InterpretationError("Error for binding \'0\'", Some(QueryGraphBuilderError(RecordNotFound("Record to delete does not exist."))))
driver_1  |     at /usr/src/app/node_modules/@prisma/client/runtime/index.js:27757:19
driver_1  |     at processTicksAndRejections (internal/process/task_queues.js:95:5)

As a workaround we are using deleteMany

Any updates on this? I have a table who can be referenced in many other tables as fk/ids at the same time, and those tables also contains other relations, i can’t update those nested relations, can’t upsert them, can’t delete them, can’t connect, can’t disconnect, not to talk about the amount of logic to determine if a object should be deleted or just updated because no operation on prisma works as described on the documentation.

A(id: 1, b: B(id: refA(1),attrs: [C(id: refAFromB(1)),C(id: refAFromB(1))]), f: F(id: refA(1), g:G(id: refA(1), h:H(id: refAFromG(1)))),);

Until now, my work arounds were based on creating transactions to slowly fix some relations until it is okay to finally update my main entity, but i guess that’s not only me based on the amount of comments here.

Here is the work around i found, have in mind i am posting this code so you guys can have an example of the problem related with trying to delete a register that doesn’t exist during a transaction (nested queries):

The strategy is: 1 - compute simple data to be updated; 2 - computed inner relationships that block deletes from happening (even with the cascade on schema); 3 - check on db all current 1x1 that exist; 4 - add the nested delete clause only if it does; 5 - update the main entity; 6 - rehydrate nested relations that shouldn’t be deleted (we should also have the ability to do that within prisma config obj in my opinion);

	addPermissionsToBaseData = (
		dto: PostUserDto,
		payload: any,
		createPermissions?: boolean,
	) => {
		let data: any = { ...payload };
	
		let alunoAux;
		let profAux;
		let pedAux;
		let dirAux;
		let secAux;
		let clienteAux;
		let pedFagAux;

		if (createPermissions) {
			// => Aluno creatw
			if (dto.aluno) {
				alunoAux = {
					create: {
						data_matricula: dto.aluno?.data_matricula,
						matricula: dto.aluno?.matricula,
						unidade: {
							connect: {
								id: dto.aluno?.unidade?.id,
							},
						},
					},
				};
			}
			// => Prof creatw
			if (dto.professor) {
				profAux = {
					create: {},
				};
			}
			// => Pedagoga creatw
			if (dto.pedagoga) {
				pedAux = {
					create: {},
				};
			}
			// => Diretor creatw
			if (dto.diretor) {
				dirAux = {
					create: {},
				};
			}
			// => Secretaria creatw
			if (dto.secretaria) {
				secAux = {
					create: {},
				};
			}
			// => Master Cliente creatw
			if (dto.master_cliente) {
				clienteAux = {
					create: {},
				};
			}
			// => Master Cliente creatw
			if (dto.pedagoga_fagundez) {
				pedFagAux = {
					create: {},
				};
			}
		}

		if (!createPermissions) {
			// => Aluno delete/update
			if (dto.aluno) {
				alunoAux = {
					upsert: {
						create: {
							data_matricula: dto.aluno.data_matricula,
							matricula: dto.aluno.matricula,
							unidade: {
								connect: {
									id: dto.aluno?.unidade?.id,
								},
							},
						},
						update: {
							data_matricula: dto.aluno.data_matricula,
							matricula: dto.aluno.matricula,
							unidade: {
								connect: {
									id: dto.aluno?.unidade?.id,
								},
							},
						},
						where: {
							id_usuario: payload.id,
						},
					},
				};
			}
			// => Professor update
			if (dto.professor) {
				profAux = {
					upsert: {
						create: {},
						update: {
							professor_unidade: {
								set: [],
							},
						},
						where: {
							id_usuario: payload.id,
						},
					},
				};
			}
			// => Pedagoga update
			if (dto.pedagoga) {
				pedAux = {
					upsert: {
						create: {},
						update: {
							pedagoga_unidade: {
								set: [],
							},
						},
						where: {
							id_usuario: payload.id,
						},
					},
				};
			}
			// => Diretor delete/update
			if (dto.diretor) {
				dirAux = {
					upsert: {
						create: {},
						update: {
							diretor_unidade: {
								set: [],
							},
						},
						where: {
							id_usuario: payload.id,
						},
					},
				};
			}
			// => Secretaria delete/update
			if (dto.secretaria) {
				secAux = {
					upsert: {
						create: {},
						update: {
							id_unidade: dto.secretaria.unidade?.id,
						},
						where: {
							id_usuario: payload.id,
						},
					},
				};
			}
			// => Master Cliente update
			if (dto.master_cliente) {
				clienteAux = {
					upsert: {
						create: {},
						update: {},
						where: {
							id_usuario: payload.id,
						},
					},
				};
			}
			// => Pedagoga Fagundez update
			if (dto.pedagoga_fagundez) {
				pedFagAux = {
					upsert: {
						create: {},
						update: {},
						where: {
							id_usuario: payload.id,
						},
					},
				};
			}
		}

		data = {
			...data,
			pedagoga_fagundez: pedFagAux,
			master_cliente: clienteAux,
			secretaria: secAux,
			diretor: dirAux,
			pedagoga: pedAux,
			professor: profAux,
			aluno: alunoAux,
		};

		return data;
	};

	// if createPermissions is false, it will automatically fall back to update
	dtoToBaseData = (
		dto: PostUserDto,
		{ createPermissions }: { createPermissions?: boolean },
	) => {
		const data: any = {
			email: dto.email,
			nome: dto.nome,
			nome_pai: dto.nome_pai,
			nome_mae: dto.nome_mae,
			data_nascimento: dto.data_nascimento,
			sexo: dto.sexo,
			cpf: dto.cpf,
			cor: dto.cor,
			etnia_indigena: dto.etnia_indigena,
		};
		return this.addPermissionsToBaseData(dto, data, createPermissions);
	};

	retrieveTelefoneData = (dto: PostUserDto) => {
		return {
			numero: dto.telefone?.numero ?? '',
		};
	};

	retrieveEnderecoData = (dto: PostUserDto) => {
		return {
			rua: dto.endereco?.rua ?? '',
			numero: dto.endereco?.numero,
			cep: dto.endereco?.cep,
			bairro: dto.endereco?.bairro,
			complemento: dto.endereco?.complemento,
			municipio: dto.endereco?.municipio,
			uf: dto.endereco?.uf,
			pais: dto.endereco?.pais,
		};
	};

	create = async (payload: PostUserDto) => {
		const tempPass =
			payload.password ?? process.env.ESPACO_MAKER_NEW_USER_PASSWORD ?? '';
		const password = await generatePasswordHash(tempPass);
		let data: any = this.dtoToBaseData(payload, { createPermissions: true });

		const telefoneData = this.retrieveTelefoneData(payload);
		const enderecoData = this.retrieveEnderecoData(payload);
		data = {
			...data,
			username: generateUsername(findMainPerfil(payload)),
			password: password,
			telefone: {
				create: telefoneData,
			},
			endereco: {
				create: enderecoData,
			},
		};
		if (payload.cliente) {
			data = {
				...data,
				cliente: {
					connect: {
						id: payload.cliente?.id,
					},
				},
			};
		}

		const userId = await this.prismaService.$transaction(async (prisma) => {
			const userEntity = await prisma.usuario.create({
				data: data,
			});
			if (payload.diretor) {
				const unidadesDiretor: any[] = [];
				(payload.diretor?.unidades ?? []).forEach((e) => {
					unidadesDiretor.push({
						id_diretor: userEntity.id,
						id_unidade: e.id,
					});
				});
				await prisma.diretor_unidade.createMany({
					data: unidadesDiretor,
				});
			}

			if (payload.pedagoga) {
				const unidadesPedagoga: any[] = [];
				(payload.pedagoga?.unidades ?? []).forEach((e) => {
					unidadesPedagoga.push({
						id_pedagoga: userEntity.id,
						id_unidade: e.id,
					});
				});
				await prisma.pedagoga_unidade.createMany({
					data: unidadesPedagoga,
				});
			}

			if (payload.professor) {
				const unidadesProfessor: any[] = [];
				(payload.professor?.unidades ?? []).forEach((e) => {
					unidadesProfessor.push({
						id_professor: userEntity.id,
						id_unidade: e.id,
					});
				});
				await prisma.professor_unidade.createMany({
					data: unidadesProfessor,
				});
			}
			return userEntity.id;
		});
		return await this.findById(userId);
	};

	update = async (payload: ReadUserDto): Promise<ReadUserDto> => {
		if (!payload.id) {
			throw new HttpException(
				{
					message: 'Informe o id do usuário que deseja alterar.',
				},
				HttpStatus.UNAUTHORIZED,
			);
		}
		let data: any = this.dtoToBaseData(payload, {});
		data = {
			...data,
			updated_at: new Date(),
		};
		const telefoneData = this.retrieveTelefoneData(payload);
		const enderecoData = this.retrieveEnderecoData(payload);

		const unidadesDiretor: any[] = [];
		(payload.diretor?.unidades ?? []).forEach((e) => {
			unidadesDiretor.push({
				id_diretor: payload.id,
				id_unidade: e.id,
			});
		});
		const unidadesPedagoga: any[] = [];
		(payload.pedagoga?.unidades ?? []).forEach((e) => {
			unidadesPedagoga.push({
				id_pedagoga: payload.id,
				id_unidade: e.id,
			});
		});
		const unidadesProfessor: any[] = [];
		(payload.professor?.unidades ?? []).forEach((e) => {
			unidadesProfessor.push({
				id_professor: payload.id,
				id_unidade: e.id,
			});
		});
		const [
			isPedFag,
			isMasterCliente,
			isSecretaria,
			isDiretor,
			isPedagoga,
			isProfessor,
			isAluno,
		] = await this.prismaService.$transaction([
			this.prismaService.pedagoga_fagundez.count({
				where: {
					id_usuario: payload.id,
				},
			}),
			this.prismaService.master_cliente.count({
				where: {
					id_usuario: payload.id,
				},
			}),
			this.prismaService.secretaria.count({
				where: {
					id_usuario: payload.id,
				},
			}),
			this.prismaService.diretor.count({
				where: {
					id_usuario: payload.id,
				},
			}),
			this.prismaService.pedagoga.count({
				where: {
					id_usuario: payload.id,
				},
			}),
			this.prismaService.professor.count({
				where: {
					id_usuario: payload.id,
				},
			}),
			this.prismaService.aluno.count({
				where: {
					id_usuario: payload.id,
				},
			}),
		]);

		if (isPedFag && !payload.pedagoga_fagundez) {
			data = {
				...data,
				pedagoga_fagundez: {
					delete: {
						id_usuario: payload.id,
					},
				},
			};
		}
		if (isMasterCliente && !payload.master_cliente) {
			data = {
				...data,
				master_cliente: {
					delete: {
						id_usuario: payload.id,
					},
				},
			};
		}
		if (isSecretaria && !payload.secretaria) {
			data = {
				...data,
				secretaria: {
					delete: {
						id_usuario: payload.id,
					},
				},
			};
		}
		if (isDiretor && !payload.diretor) {
			data = {
				...data,
				diretor: {
					delete: {
						id_usuario: payload.id,
					},
				},
			};
		}
		if (isPedagoga && !payload.pedagoga) {
			data = {
				...data,
				pedagoga: {
					delete: {
						id_usuario: payload.id,
					},
				},
			};
		}
		if (isProfessor && !payload.professor) {
			data = {
				...data,
				professor: {
					delete: {
						id_usuario: payload.id,
					},
				},
			};
		}
		if (isAluno && !payload.aluno) {
			data = {
				...data,
				aluno: {
					delete: {
						id_usuario: payload.id,
					},
				},
			};
		}

		const userId = await this.prismaService.$transaction(
			async (prisma) => {
				// This will be rehydrated by upserts, but trying to delete
				// these records will fail the entire transaction if they are
				// made into the update, and the id being deleted doesn't exist.
				await Promise.all([
					prisma.diretor_unidade.deleteMany({
						where: {
							id_diretor: payload.id,
						},
					}),
					// with new delete/update logic, this might be able to be deleted
					prisma.pedagoga_unidade.deleteMany({
						where: {
							id_pedagoga: payload.id,
						},
					}),
					prisma.professor_unidade.deleteMany({
						where: {
							id_professor: payload.id,
						},
					}),
				]);
				const entity = await prisma.usuario.update({
					where: {
						id: payload.id,
					},
					data: {
						...data,
						telefone: {
							upsert: {
								create: telefoneData,
								update: {
									...telefoneData,
									updated_at: new Date(),
								},
							},
						},
						endereco: {
							upsert: {
								create: enderecoData,
								update: {
									...enderecoData,
									updated_at: new Date(),
								},
							},
						},
					},
				});
				await Promise.all([
					prisma.diretor_unidade.createMany({
						data: unidadesDiretor,
					}),
					prisma.pedagoga_unidade.createMany({
						data: unidadesPedagoga,
					}),
					prisma.professor_unidade.createMany({
						data: unidadesProfessor,
					}),
				]);
				return entity.id;
			},
			{
				maxWait: 10000, // default: 2000 - how long can the transaction take
				timeout: 10000, // default: 5000 - how long will the user await for a connection
				// into the pool before rejecting to even start the transaction
				isolationLevel: Prisma.TransactionIsolationLevel.Serializable, // optional, default defined by database configuration
			},
		);

		return await this.findById(userId);
	};

I’m using deleteMany for now, since the email field is unique, I know it’s a safe operation

prisma.user.deleteMany({
    where: { email }
}),