@@ -627,7 +627,31 @@ protected function deleteDescendants()
627
627
? 'forceDelete '
628
628
: 'delete ' ;
629
629
630
- $ this ->descendants ()->{$ method }();
630
+ // We must delete the nodes in correct order to avoid failing
631
+ // foreign key constraints when we delete an entire subtree.
632
+ // For MySQL we must avoid that a parent is deleted before its
633
+ // children although the complete subtree will be deleted eventually.
634
+ // Hence, deletion must start with the deepest node, i.e. with the
635
+ // highest _lft value first.
636
+ // Note: `DELETE ... ORDER BY` is non-standard SQL but required by
637
+ // MySQL (see https://dev.mysql.com/doc/refman/8.0/en/delete.html),
638
+ // because MySQL only supports "row consistency".
639
+ // This means the DB must be consistent before and after every single
640
+ // operation on a row.
641
+ // This is contrasted by statement and transaction consistency which
642
+ // means that the DB must be consistent before and after every
643
+ // completed statement/transaction.
644
+ // (See https://dev.mysql.com/doc/refman/8.0/en/ansi-diff-foreign-keys.html)
645
+ // ANSI Standard SQL requires support for statement/transaction
646
+ // consistency, but only PostgreSQL supports it.
647
+ // (Good PosgreSQL :-) )
648
+ // PostgreSQL does not support `DELETE ... ORDER BY` but also has no
649
+ // need for it.
650
+ // The grammar compiler removes the superfluous "ORDER BY" for
651
+ // PostgreSQL.
652
+ $ this ->descendants ()
653
+ ->orderBy ($ this ->getLftName (), 'desc ' )
654
+ ->{$ method }();
631
655
632
656
if ($ this ->hardDeleting ()) {
633
657
$ height = $ rgt - $ lft + 1 ;
0 commit comments