-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Cascade deletion complicate-referenced entities does not work [ForeignKeyConstraintViolationException] #5665
Comments
Seems normal to me:
In this scenario, you need to define a DB-level cascade operation via the |
DB-level cascade will not trigger lifecycle events. If this scenario seem normal to you, I could give you another The flush is still throw ForeignKeyConstraintViolationException. Entities cannot be removed at ORM level without calling flush more than 1 time. Is this normal? |
Indeed, but a delete with a cascade won't trigger any deletes until mid-flush, so lifecycle events on some cascaded items will still work (first level of cascading) |
You have a cyclic constraint there: that's not something the ORM can solve for you right now |
But it solve insertions properly by preassigning NULL in INSERT query and assigning generated identy in update query. So it should do the same at deletion in reverse order. Update column to NULL before deleting. |
That isn't currently done by the ORM as far as I know (I would have to check the test suite, but no time ATM), mostly because the column may be non-nullable |
In this case it is impossible. If it is non-nullable you cannot insert cyclic referenced entity. So first flush for persisting will fail. |
Good point indeed. I suggest digging in the existing test suite for similar cases though, as this behavior may indeed be improved. |
@mtal I'd ask you to try the same case using latest master. I also implemented a couple changes in the OrphanRemoval that should properly remove orphaned on *ToMany and might be related. Can you please test this against master and comment back? We might extract a sample TestCase from this for us to look if both situations didn't cover your issue. |
Delete order is changed but issue is still persist. Exception is still thrown.
The commit order cannot fix it. Before deleting row, another that references it, must be updated. Referencing column must be updated to null.
The cyclic insertion was work properly before. The issue in impossibility to delete inside single flush. |
And without actual (persisted cyclic references) deletion still fails. Commit order calculated incorrectly. Look at this case: $em->beginTransaction();
$item = new Item();
$sub = new Subitem();
$item->addItem($sub);
$subsub = new Subsubitem();
$sub->setSub($subsub);
$em->persist($item);
$em->flush();
$em->remove($item);
$em->flush();
$em->rollback(); Exception The database log
The 'subitem' and 'subsubitem' must be deleted before Mapping: <?php
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Item
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var ArrayCollection
*
* @ORM\OneToMany(targetEntity="Subitem", mappedBy="item", cascade={"all"}, orphanRemoval=true)
*/
protected $items;
/**
* @var Subitem
*
* @ORM\ManyToOne(targetEntity="Subitem")
*/
protected $featuredItem;
public function __construct()
{
$this->items = new ArrayCollection();
}
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @return ArrayCollection
*/
public function getItems()
{
return $this->items;
}
public function addItem(Subitem $item)
{
$this->items[] = $item;
$item->setItem($this);
}
public function removeItem(Subitem $item) {
if ($this->featuredItem === $item) {
$this->featuredItem = null;
}
$this->items->removeElement($item);
}
/**
* @return Subitem
*/
public function getFeaturedItem()
{
return $this->featuredItem;
}
/**
* @param Subitem $featuredItem
*/
public function setFeaturedItem(Subitem $featuredItem = null)
{
$this->featuredItem = $featuredItem;
}
}
/**
* @ORM\Entity
*/
class Subitem
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var Item
*
* @ORM\ManyToOne(targetEntity="Item", inversedBy="items")
*/
protected $item;
/**
* @var Subsubitem
*
* @ORM\OneToOne(targetEntity="Subsubitem", cascade={"all"})
*/
protected $sub;
/**
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* @return Item
*/
public function getItem()
{
return $this->item;
}
/**
* @param Item $item
*/
public function setItem($item)
{
$this->item = $item;
}
/**
* @return Subsubitem
*/
public function getSub()
{
return $this->sub;
}
/**
* @param Subsubitem $sub
*/
public function setSub(Subsubitem $sub = null)
{
$this->sub = $sub;
}
}
/**
* @ORM\Entity
*/
class Subsubitem
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @return int
*/
public function getId()
{
return $this->id;
}
} |
Guys, any info on resolving this issue? I have user->galleries->photos->images and I have foreign key constraint error mentioned in topic when trying to remove user. I should make onDelete="CASCADE" in this case or remove everything by hand? UPDATE: onDelete="CASCADE" does not event make difference for migration to database, so I cannot use this. |
up |
@slince that doesn't help - open a PR with a reproducible failing test case |
Hi, the problem I encountered is the same as this one |
@slince yes, but upvoting doesn't get anywhere on a project without dedicated staff. You gotta help yourself too ;-) |
Hi, are there a solution for this problem? recently I update mi libraries and a update process trigger this error. my mapping aparently is ok, "persist" and "remove" from owner and onDelete:Cascade from other side. Any idea about solution? |
…ut DB-level cascade This adds a failing test case for doctrine#5665. In this example, we have a cyclic association between three entities. All associations are NULLable, so the ORM is able to perform the INSERT operation: The cycle can be broken by scheduling an "extra UPDATE" in the UoW. However, the UoW is unable to perform the remove operation. Cyclic references by the foreign keys in the database prevent removal of two of the entities. If the ORM were able to detect this case and perform an UPDATE _before_ the DELETE, the test would pass. As a workaround, `@JoinColumn(onDelete="CASCADE")` can be used. That way, the DBMS will make this UPDATE just in time and without ORM support – but that seems not to be what the OP of doctrine#5665 asked for.
Update 2023: To summarize what has been written above, the problem is not finding an insert order for the cyclic associations. The UoW is able to solve this by scheduling "extra updates". This is possible since all associations are NULLable (by default!). The problem lies in the DELETE operation, where the ORM does currently not schedule extra updates before the DELETE to break association cycles. A failing test showing the lack of this feature can be found in #10548. But even when the user configures |
When computing the commit order for entity removals, we have to look out for `@ORM\JoinColumn(onDelete="SET NULL")` to find places where cyclic associations can be broken. #### Background The UoW computes a "commit order" to find the sequence in which tables shall be processed when inserting entities into the database or performing delete operations. For the insert case, the ORM is able to schedule _extra updates_ that will be performed after all entities have been inserted. Associations which are configured as `@ORM\JoinColumn(nullable=true, ...)` can be left as `NULL` in the database when performing the initial `INSERT` statements, and will be updated once all new entities have been written to the database. This can be used to break cyclic associations between entity instances. For removals, the ORM does not currently implement up-front `UPDATE` statements to `NULL` out associations before `DELETE` statements are executed. That means when associations form a cycle, users have to configure `@ORM\JoinColumn(onDelete="SET NULL", ...)` on one of the associations involved. This transfers responsibility to the DBMS to break the cycle at that place. _But_, we still have to perform the delete statements in an order that makes this happen early enough. This may be a _different_ order than the one required for the insert case. We can find it _only_ by looking at the `onDelete` behaviour. We must ignore the `nullable` property, which is irrelevant, since we do not even try to `NULL` anything. #### Example Assume three entity classes `A`, `B`, `C`. There are unidirectional one-to-one associations `A -> B`, `B -> C`, `C -> A`. All those associations are `nullable= true`. Three entities `$a`, `$b`, `$c` are created from these respective classes and associations are set up. All operations `cascade` at the ORM level. So we can test what happens when we start the operations at the three individual entities, but in the end, they will always involve all three of them. _Any_ insert order will work, so the improvements necessary to solve doctrine#10531 or doctrine#10532 are not needed here. Since all associations are between different tables, the current table-level computation is good enough. For the removal case, only the `A -> B` association has `onDelete="SET NULL"`. So, the only possible execution order is `$b`, `$c`, `$a`. We have to find that regardless of where we start the cascade operation. The DBMS will set the `A -> B` association on `$a` to `NULL` when we remove `$b`. We can then remove `$c` since it is no longer being referred to, then `$a`. #### Related cases These cases ask for the ORM to perform the extra update before the delete by itself, without DBMS-level support: * doctrine#5665 * doctrine#10548
When computing the commit order for entity removals, we have to look out for `@ORM\JoinColumn(onDelete="SET NULL")` to find places where cyclic associations can be broken. #### Background The UoW computes a "commit order" to find the sequence in which tables shall be processed when inserting entities into the database or performing delete operations. For the insert case, the ORM is able to schedule _extra updates_ that will be performed after all entities have been inserted. Associations which are configured as `@ORM\JoinColumn(nullable=true, ...)` can be left as `NULL` in the database when performing the initial `INSERT` statements, and will be updated once all new entities have been written to the database. This can be used to break cyclic associations between entity instances. For removals, the ORM does not currently implement up-front `UPDATE` statements to `NULL` out associations before `DELETE` statements are executed. That means when associations form a cycle, users have to configure `@ORM\JoinColumn(onDelete="SET NULL", ...)` on one of the associations involved. This transfers responsibility to the DBMS to break the cycle at that place. _But_, we still have to perform the delete statements in an order that makes this happen early enough. This may be a _different_ order than the one required for the insert case. We can find it _only_ by looking at the `onDelete` behaviour. We must ignore the `nullable` property, which is irrelevant, since we do not even try to `NULL` anything. #### Example Assume three entity classes `A`, `B`, `C`. There are unidirectional one-to-one associations `A -> B`, `B -> C`, `C -> A`. All those associations are `nullable= true`. Three entities `$a`, `$b`, `$c` are created from these respective classes and associations are set up. All operations `cascade` at the ORM level. So we can test what happens when we start the operations at the three individual entities, but in the end, they will always involve all three of them. _Any_ insert order will work, so the improvements necessary to solve doctrine#10531 or doctrine#10532 are not needed here. Since all associations are between different tables, the current table-level computation is good enough. For the removal case, only the `A -> B` association has `onDelete="SET NULL"`. So, the only possible execution order is `$b`, `$c`, `$a`. We have to find that regardless of where we start the cascade operation. The DBMS will set the `A -> B` association on `$a` to `NULL` when we remove `$b`. We can then remove `$c` since it is no longer being referred to, then `$a`. #### Related cases These cases ask for the ORM to perform the extra update before the delete by itself, without DBMS-level support: * doctrine#5665 * doctrine#10548
When computing the commit order for entity removals, we have to look out for `@ORM\JoinColumn(onDelete="SET NULL")` to find places where cyclic associations can be broken. #### Background The UoW computes a "commit order" to find the sequence in which tables shall be processed when inserting entities into the database or performing delete operations. For the insert case, the ORM is able to schedule _extra updates_ that will be performed after all entities have been inserted. Associations which are configured as `@ORM\JoinColumn(nullable=true, ...)` can be left as `NULL` in the database when performing the initial `INSERT` statements, and will be updated once all new entities have been written to the database. This can be used to break cyclic associations between entity instances. For removals, the ORM does not currently implement up-front `UPDATE` statements to `NULL` out associations before `DELETE` statements are executed. That means when associations form a cycle, users have to configure `@ORM\JoinColumn(onDelete="SET NULL", ...)` on one of the associations involved. This transfers responsibility to the DBMS to break the cycle at that place. _But_, we still have to perform the delete statements in an order that makes this happen early enough. This may be a _different_ order than the one required for the insert case. We can find it _only_ by looking at the `onDelete` behaviour. We must ignore the `nullable` property, which is irrelevant, since we do not even try to `NULL` anything. #### Example Assume three entity classes `A`, `B`, `C`. There are unidirectional one-to-one associations `A -> B`, `B -> C`, `C -> A`. All those associations are `nullable= true`. Three entities `$a`, `$b`, `$c` are created from these respective classes and associations are set up. All operations `cascade` at the ORM level. So we can test what happens when we start the operations at the three individual entities, but in the end, they will always involve all three of them. _Any_ insert order will work, so the improvements necessary to solve doctrine#10531 or doctrine#10532 are not needed here. Since all associations are between different tables, the current table-level computation is good enough. For the removal case, only the `A -> B` association has `onDelete="SET NULL"`. So, the only possible execution order is `$b`, `$c`, `$a`. We have to find that regardless of where we start the cascade operation. The DBMS will set the `A -> B` association on `$a` to `NULL` when we remove `$b`. We can then remove `$c` since it is no longer being referred to, then `$a`. #### Related cases These cases ask for the ORM to perform the extra update before the delete by itself, without DBMS-level support: * doctrine#5665 * doctrine#10548
When computing the commit order for entity removals, we have to look out for `@ORM\JoinColumn(onDelete="SET NULL")` to find places where cyclic associations can be broken. #### Background The UoW computes a "commit order" to find the sequence in which tables shall be processed when inserting entities into the database or performing delete operations. For the insert case, the ORM is able to schedule _extra updates_ that will be performed after all entities have been inserted. Associations which are configured as `@ORM\JoinColumn(nullable=true, ...)` can be left as `NULL` in the database when performing the initial `INSERT` statements, and will be updated once all new entities have been written to the database. This can be used to break cyclic associations between entity instances. For removals, the ORM does not currently implement up-front `UPDATE` statements to `NULL` out associations before `DELETE` statements are executed. That means when associations form a cycle, users have to configure `@ORM\JoinColumn(onDelete="SET NULL", ...)` on one of the associations involved. This transfers responsibility to the DBMS to break the cycle at that place. _But_, we still have to perform the delete statements in an order that makes this happen early enough. This may be a _different_ order than the one required for the insert case. We can find it _only_ by looking at the `onDelete` behaviour. We must ignore the `nullable` property, which is irrelevant, since we do not even try to `NULL` anything. #### Example Assume three entity classes `A`, `B`, `C`. There are unidirectional one-to-one associations `A -> B`, `B -> C`, `C -> A`. All those associations are `nullable= true`. Three entities `$a`, `$b`, `$c` are created from these respective classes and associations are set up. All operations `cascade` at the ORM level. So we can test what happens when we start the operations at the three individual entities, but in the end, they will always involve all three of them. _Any_ insert order will work, so the improvements necessary to solve doctrine#10531 or doctrine#10532 are not needed here. Since all associations are between different tables, the current table-level computation is good enough. For the removal case, only the `A -> B` association has `onDelete="SET NULL"`. So, the only possible execution order is `$b`, `$c`, `$a`. We have to find that regardless of where we start the cascade operation. The DBMS will set the `A -> B` association on `$a` to `NULL` when we remove `$b`. We can then remove `$c` since it is no longer being referred to, then `$a`. #### Related cases These cases ask for the ORM to perform the extra update before the delete by itself, without DBMS-level support: * doctrine#5665 * doctrine#10548
When computing the commit order for entity removals, we have to look out for `@ORM\JoinColumn(onDelete="SET NULL")` to find places where cyclic associations can be broken. #### Background The UoW computes a "commit order" to find the sequence in which tables shall be processed when inserting entities into the database or performing delete operations. For the insert case, the ORM is able to schedule _extra updates_ that will be performed after all entities have been inserted. Associations which are configured as `@ORM\JoinColumn(nullable=true, ...)` can be left as `NULL` in the database when performing the initial `INSERT` statements, and will be updated once all new entities have been written to the database. This can be used to break cyclic associations between entity instances. For removals, the ORM does not currently implement up-front `UPDATE` statements to `NULL` out associations before `DELETE` statements are executed. That means when associations form a cycle, users have to configure `@ORM\JoinColumn(onDelete="SET NULL", ...)` on one of the associations involved. This transfers responsibility to the DBMS to break the cycle at that place. _But_, we still have to perform the delete statements in an order that makes this happen early enough. This may be a _different_ order than the one required for the insert case. We can find it _only_ by looking at the `onDelete` behaviour. We must ignore the `nullable` property, which is irrelevant, since we do not even try to `NULL` anything. #### Example Assume three entity classes `A`, `B`, `C`. There are unidirectional one-to-one associations `A -> B`, `B -> C`, `C -> A`. All those associations are `nullable= true`. Three entities `$a`, `$b`, `$c` are created from these respective classes and associations are set up. All operations `cascade` at the ORM level. So we can test what happens when we start the operations at the three individual entities, but in the end, they will always involve all three of them. _Any_ insert order will work, so the improvements necessary to solve doctrine#10531 or doctrine#10532 are not needed here. Since all associations are between different tables, the current table-level computation is good enough. For the removal case, only the `A -> B` association has `onDelete="SET NULL"`. So, the only possible execution order is `$b`, `$c`, `$a`. We have to find that regardless of where we start the cascade operation. The DBMS will set the `A -> B` association on `$a` to `NULL` when we remove `$b`. We can then remove `$c` since it is no longer being referred to, then `$a`. #### Related cases These cases ask for the ORM to perform the extra update before the delete by itself, without DBMS-level support: * doctrine#5665 * doctrine#10548
…10566) When computing the commit order for entity removals, we have to look out for `@ORM\JoinColumn(onDelete="SET NULL")` to find places where cyclic associations can be broken. #### Background The UoW computes a "commit order" to find the sequence in which tables shall be processed when inserting entities into the database or performing delete operations. For the insert case, the ORM is able to schedule _extra updates_ that will be performed after all entities have been inserted. Associations which are configured as `@ORM\JoinColumn(nullable=true, ...)` can be left as `NULL` in the database when performing the initial `INSERT` statements, and will be updated once all new entities have been written to the database. This can be used to break cyclic associations between entity instances. For removals, the ORM does not currently implement up-front `UPDATE` statements to `NULL` out associations before `DELETE` statements are executed. That means when associations form a cycle, users have to configure `@ORM\JoinColumn(onDelete="SET NULL", ...)` on one of the associations involved. This transfers responsibility to the DBMS to break the cycle at that place. _But_, we still have to perform the delete statements in an order that makes this happen early enough. This may be a _different_ order than the one required for the insert case. We can find it _only_ by looking at the `onDelete` behaviour. We must ignore the `nullable` property, which is irrelevant, since we do not even try to `NULL` anything. #### Example Assume three entity classes `A`, `B`, `C`. There are unidirectional one-to-one associations `A -> B`, `B -> C`, `C -> A`. All those associations are `nullable= true`. Three entities `$a`, `$b`, `$c` are created from these respective classes and associations are set up. All operations `cascade` at the ORM level. So we can test what happens when we start the operations at the three individual entities, but in the end, they will always involve all three of them. _Any_ insert order will work, so the improvements necessary to solve #10531 or #10532 are not needed here. Since all associations are between different tables, the current table-level computation is good enough. For the removal case, only the `A -> B` association has `onDelete="SET NULL"`. So, the only possible execution order is `$b`, `$c`, `$a`. We have to find that regardless of where we start the cascade operation. The DBMS will set the `A -> B` association on `$a` to `NULL` when we remove `$b`. We can then remove `$c` since it is no longer being referred to, then `$a`. #### Related cases These cases ask for the ORM to perform the extra update before the delete by itself, without DBMS-level support: * #5665 * #10548
…ut DB-level cascade This adds a failing test case for doctrine#5665. In this example, we have a cyclic association between three entities. All associations are NULLable, so the ORM is able to perform the INSERT operation: The cycle can be broken by scheduling an "extra UPDATE" in the UoW. However, the UoW is unable to perform the remove operation. Cyclic references by the foreign keys in the database prevent removal of two of the entities. If the ORM were able to detect this case and perform an UPDATE _before_ the DELETE, the test would pass. As a workaround, `@JoinColumn(onDelete="CASCADE")` can be used. That way, the DBMS will make this UPDATE just in time and without ORM support – but that seems not to be what the OP of doctrine#5665 asked for.
Update 2024: The ORM is not (yet?) able to perform extra Since #10566, the ORM should schedule removals in the appropriate order. |
is caused by
Entity mappings:
The text was updated successfully, but these errors were encountered: