Skip to content

Commit 80d5c39

Browse files
committed
Delete orphaned FK rows via GC
1 parent d92f873 commit 80d5c39

2 files changed

Lines changed: 54 additions & 0 deletions

File tree

CHANGELOG-WIP.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@
88
- The Queue Manager utility now shows jobs’ class names. ([#16228](https://github.com/craftcms/cms/pull/16228))
99

1010
## System
11+
- Database rows with foreign keys referencing nonexistent rows are now deleted via garbage collection.
1112
- Updated Twig to 3.15. ([#16207](https://github.com/craftcms/cms/discussions/16207))

src/services/Gc.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use craft\db\Connection;
1616
use craft\db\Query;
1717
use craft\db\Table;
18+
use craft\db\TableSchema;
1819
use craft\elements\Asset;
1920
use craft\elements\Category;
2021
use craft\elements\Entry;
@@ -151,6 +152,7 @@ public function run(bool $force = false): void
151152
$this->_deleteOrphanedSearchIndexes();
152153
$this->_deleteOrphanedRelations();
153154
$this->_deleteOrphanedStructureElements();
155+
$this->_deleteOrphanedFkRows();
154156

155157
$this->_hardDeleteStructures();
156158

@@ -525,6 +527,57 @@ private function _deleteOrphanedStructureElements(): void
525527
$this->_stdout("done\n", Console::FG_GREEN);
526528
}
527529

530+
private function _deleteOrphanedFkRows(): void
531+
{
532+
$this->_stdout(' > deleting orphaned foreign key rows ... ');
533+
534+
// Disable FK checks
535+
$qb = $this->db->getSchema()->getQueryBuilder();
536+
$this->db->createCommand($qb->checkIntegrity(false))->execute();
537+
538+
$isMysql = $this->db->getIsMysql();
539+
foreach ($this->db->getSchema()->getTableSchemas() as $table) {
540+
/** @var TableSchema $table */
541+
$extendedFkInfo = $table->getExtendedForeignKeys();
542+
$counter = 0;
543+
foreach ($table->foreignKeys as $fk) {
544+
if ($extendedFkInfo[$counter]['deleteType'] === 'CASCADE') {
545+
$fk = array_merge($fk);
546+
$refTable = array_shift($fk);
547+
548+
foreach ($fk as $fkColumn => $pkColumn) {
549+
if ($isMysql) {
550+
$sql = <<<SQL
551+
DELETE t.* FROM $table->name t
552+
LEFT JOIN $refTable t2 ON t2.$pkColumn = t.$fkColumn
553+
WHERE t.$fkColumn IS NOT NULL
554+
AND t2.$pkColumn IS NULL
555+
SQL;
556+
} else {
557+
$sql = <<<SQL
558+
DELETE FROM $table->name t
559+
WHERE t2.$pkColumn IS NULL
560+
AND NOT EXISTS (
561+
SELECT * FROM $refTable
562+
WHERE "$pkColumn" = t."$fkColumn"
563+
)
564+
SQL;
565+
}
566+
567+
$this->db->createCommand($sql)->execute();
568+
}
569+
}
570+
571+
$counter++;
572+
}
573+
}
574+
575+
// Re-enable FK checks
576+
$this->db->createCommand($qb->checkIntegrity())->execute();
577+
578+
$this->_stdout("done\n", Console::FG_GREEN);
579+
}
580+
528581
/**
529582
* Deletes field layouts that are no longer used.
530583
*

0 commit comments

Comments
 (0)