doctrine / orm

Doctrine Object Relational Mapper (ORM)
https://www.doctrine-project.org/projects/orm.html
MIT License
9.86k stars 2.5k forks source link

Emptying collection containing Single-Inheritence Discriminated Entity uses dangerous DELETE statement #11500

Open gitbugr opened 1 week ago

gitbugr commented 1 week ago

Bug Report

Q A
BC Break no
Version 2.19.5

Summary

Emptying collection containing Single-Inheritence Discriminated Entity uses a dangerous DELETE statements that can lead to unintentionally removed records for different entities within the same single table inheritance.

Current behavior

When an Entity UserA has a property things which is a OneToMany relation with orphanRemoval enabled to an Entity ThingA (references UserA via a property of owner) which is part of a single table hierarchy (using discriminator mapping) extending from AbstractThing, doing the following:

$userA->getThings()->clear();

and persisting+flushing causes the db to receive iterated DELETE statements for the records in the Collection (e.g. DELETE FROM things WHERE id = 1; DELETE FROM things WHERE id = 2; #... etc.)

If instead you do:

class UserA {
    public function setThings(ArrayCollection $things): void
    {
        $this->things = $things;
    }
...
}
...

$userA->setThings(new ArrayCollection());

and persist+flush, then the database instead receives a request of the form DELETE FROM things WHERE owner_id = 1;, without the discriminator column in the WHERE clause.

This can cause a problem in the instance where another Entity in the same hierarchy, ThingB, has an association to a different entity, UserB, using the same property name since this could lead to collisions in the UserA/UserB owned records leading to entities from one being removed due to the deletion of those associated with the other.

How to reproduce

Minimal reproducible example: https://github.com/gitbugr/doctrine-assoc-delete-example

Expected behavior

I would expect the delete directive to include the discriminator column in addition to the id. Or, if $userA->setThings(new ArrayCollection()); is improper, it should be guarded against.