[PR #10913] Include ON DELETE CASCADE associations in the delete order computation #12693

Closed
opened 2026-01-22 16:14:52 +01:00 by admin · 0 comments
Owner

Original Pull Request: https://github.com/doctrine/orm/pull/10913

State: closed
Merged: Yes


In order to resolve #10348, some changes were included in #10547 to improve the computed delete order for entities.

One assumption was that foreign key references with ON DELETE SET NULL or ... CASCADE need not need to be taken into consideration when planning the deletion order, since the RDBMS would unset or cascade-delete such associations by itself when necessary. Only associations that do not use RDBMS-level cascade handling would be sequenced, to make sure the referring entity is deleted before the referred-to one.

This assumption is wrong for ON DELETE CASCADE. The following examples give reasons why we need to also consider such associations, and in addition, we need to be able to deal with cycles formed by them.

In the following diagrams, odc means ON DELETE CASCADE, and ref is a regular foreign key with no extra ON DELETE semantics.

graph LR;
C-->|ref| B;
B-->|odc| A;

In this example, C must be removed before B and A. If we ignore the B->A dependency in the delete order computation, the result may not to be correct. ACB is not a working solution.

graph LR;
A-->|odc| B;
B-->|odc| A;
C-->|ref| B;

This is the situation in #10912. We have to deal with a cycle in the graph. C must be removed before A as well as B. If we ignore the B->A dependency (e.g. because we set it to "optional" to get away with the cycle), we might end up with an incorrect order ACB.

graph LR;
A-->|odc| B;
B-->|odc| A;
A-->|ref| C;
C-->|ref| B;

This example has no possible remove order. But, if we treat odc edges as optional, A -> C -> B would wrongly be deemed suitable.

graph LR;
A-->|ref| B;
B-->|odc| C;
C-->|odc| B;
D-->|ref| C;

Here, we must first remove A and D in any order; then, B and C in any order. If we treat one of the odc edges as optional, we might find the invalid solutions ABDC or DCAB.

Solution implemented in this PR

First, build a graph with a node for every to-be-removed entity, and edges for ON DELETE CASCADE associations between those entities. Then, use Tarjan's algorithm to find strongly connected components (SCCs) in this graph. The significance of SCCs is that whenever we remove one of the entities in a SCC from the database (no matter which one), the DBMS will immediately remove all the other entities of that group as well.

For every SCC, pick one (arbitrary) entity from the group to represent all entities of that group.

Then, build a second graph. Again we have nodes for all entities that are to be removed. This time, we insert edges for all regular (foreign key) associations and those with ON DELETE CASCADE. ON DELETE SET NULL can be left out. The edges are not added between the entities themselves, but between the entities representing the respective SCCs.

Also, for all non-trivial SCCs (those containing more than a single entity), add dependency edges to indicate that all entities of the SCC shall be processed after the entity representing the group. This is to make sure we do not remove a SCC inadvertedly by removing one of its entities too early.

Run a topological sort on the second graph to get the actual delete order. Cycles in this second graph are a problem, there is no delete order.

Fixes #10912.

**Original Pull Request:** https://github.com/doctrine/orm/pull/10913 **State:** closed **Merged:** Yes --- In order to resolve #10348, some changes were included in #10547 to improve the computed _delete_ order for entities. One assumption was that foreign key references with `ON DELETE SET NULL` or `... CASCADE` need not need to be taken into consideration when planning the deletion order, since the RDBMS would unset or cascade-delete such associations by itself when necessary. Only associations that do _not_ use RDBMS-level cascade handling would be sequenced, to make sure the referring entity is deleted before the referred-to one. This assumption is wrong for `ON DELETE CASCADE`. The following examples give reasons why we need to also consider such associations, and in addition, we need to be able to deal with cycles formed by them. In the following diagrams, `odc` means `ON DELETE CASCADE`, and `ref` is a regular foreign key with no extra `ON DELETE` semantics. ```mermaid graph LR; C-->|ref| B; B-->|odc| A; ``` In this example, C must be removed before B and A. If we ignore the B->A dependency in the delete order computation, the result may not to be correct. ACB is not a working solution. ```mermaid graph LR; A-->|odc| B; B-->|odc| A; C-->|ref| B; ``` This is the situation in #10912. We have to deal with a cycle in the graph. C must be removed before A as well as B. If we ignore the B->A dependency (e.g. because we set it to "optional" to get away with the cycle), we might end up with an incorrect order ACB. ```mermaid graph LR; A-->|odc| B; B-->|odc| A; A-->|ref| C; C-->|ref| B; ``` This example has no possible remove order. But, if we treat `odc` edges as optional, A -> C -> B would wrongly be deemed suitable. ```mermaid graph LR; A-->|ref| B; B-->|odc| C; C-->|odc| B; D-->|ref| C; ``` Here, we must first remove A and D in any order; then, B and C in any order. If we treat one of the `odc` edges as optional, we might find the invalid solutions ABDC or DCAB. #### Solution implemented in this PR First, build a graph with a node for every to-be-removed entity, and edges for `ON DELETE CASCADE` associations between those entities. Then, use [Tarjan's algorithm](https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm) to find strongly connected components (SCCs) in this graph. The significance of SCCs is that whenever we remove one of the entities in a SCC from the database (no matter which one), the DBMS will immediately remove _all_ the other entities of that group as well. For every SCC, pick one (arbitrary) entity from the group to represent all entities of that group. Then, build a second graph. Again we have nodes for all entities that are to be removed. This time, we insert edges for all regular (foreign key) associations and those with `ON DELETE CASCADE`. `ON DELETE SET NULL` can be left out. The edges are not added between the entities themselves, but between the entities representing the respective SCCs. Also, for all non-trivial SCCs (those containing more than a single entity), add dependency edges to indicate that all entities of the SCC shall be processed _after_ the entity representing the group. This is to make sure we do not remove a SCC inadvertedly by removing one of its entities too early. Run a topological sort on the second graph to get the actual delete order. Cycles in this second graph are a problem, there is no delete order. Fixes #10912.
admin added the pull-request label 2026-01-22 16:14:52 +01:00
admin closed this issue 2026-01-22 16:14:52 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#12693