@@ -627,7 +627,31 @@ protected function deleteDescendants()
627627 ? 'forceDelete '
628628 : 'delete ' ;
629629
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 }();
631655
632656 if ($ this ->hardDeleting ()) {
633657 $ height = $ rgt - $ lft + 1 ;
0 commit comments