Skip to content

Commit

Permalink
Backport 45d7895
Browse files Browse the repository at this point in the history
Fixes #9905 for Craft 3.8
  • Loading branch information
brandonkelly committed Feb 21, 2023
1 parent d30dc43 commit fc799d9
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- Added `craft\base\Element::indexElements()`.
- Added `craft\base\ElementInterface::findSource()`.
- Added `craft\base\ElementInterface::indexElementCount()`.
- Added `craft\db\Migration::dropForeignKeyIfExists()`.
- Added `craft\models\VolumeFolder::getHasChildren()`.
- Added `craft\models\VolumeFolder::setHasChildren()`.
- Added `craft\services\Assets::createFolderQuery()`.
Expand All @@ -40,3 +41,4 @@
- The custom `activate` jQuery event will no longer trigger for <kbd>Ctrl</kbd>/<kbd>Command</kbd>-clicks.

### System
- Fixed a database deadlock error that could occur when updating a relation or structure position for an element that was simultaneously being saved. ([#9905](https://github.com/craftcms/cms/issues/9905))
2 changes: 1 addition & 1 deletion src/config/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
'id' => 'CraftCMS',
'name' => 'Craft CMS',
'version' => '3.7.67',
'schemaVersion' => '3.7.33',
'schemaVersion' => '3.8.0.0',
'minVersionRequired' => '2.6.2788',
'basePath' => dirname(__DIR__), // Defines the @app alias
'runtimePath' => '@storage/runtime', // Defines the @runtime alias
Expand Down
13 changes: 13 additions & 0 deletions src/db/Migration.php
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,19 @@ public function dropIndexIfExists(string $table, $columns, bool $unique): void
MigrationHelper::dropIndexIfExists($table, $columns, $unique, $this);
}

/**
* Builds and executes a SQL statement for dropping a foreign key, if it exists.
*
* @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
* @param string|string[] $columns the column(s) that are included in the index. If there are multiple columns, please separate them
* by commas or use an array.
* @since 3.8.0
*/
public function dropForeignKeyIfExists(string $table, $columns): void
{
MigrationHelper::dropForeignKeyIfExists($table, $columns, $this);
}

/**
* Creates and executes a SQL statement for soft-deleting a row.
*
Expand Down
2 changes: 0 additions & 2 deletions src/migrations/Install.php
Original file line number Diff line number Diff line change
Expand Up @@ -1034,7 +1034,6 @@ public function addForeignKeys()
$this->addForeignKey(null, Table::RELATIONS, ['fieldId'], Table::FIELDS, ['id'], 'CASCADE', null);
$this->addForeignKey(null, Table::RELATIONS, ['sourceId'], Table::ELEMENTS, ['id'], 'CASCADE', null);
$this->addForeignKey(null, Table::RELATIONS, ['sourceSiteId'], Table::SITES, ['id'], 'CASCADE', 'CASCADE');
$this->addForeignKey(null, Table::RELATIONS, ['targetId'], Table::ELEMENTS, ['id'], 'CASCADE', null);
$this->addForeignKey(null, Table::REVISIONS, ['creatorId'], Table::USERS, ['id'], 'SET NULL', null);
$this->addForeignKey(null, Table::REVISIONS, ['sourceId'], Table::ELEMENTS, ['id'], 'CASCADE', null);
$this->addForeignKey(null, Table::SECTIONS, ['structureId'], Table::STRUCTURES, ['id'], 'SET NULL', null);
Expand All @@ -1043,7 +1042,6 @@ public function addForeignKeys()
$this->addForeignKey(null, Table::SESSIONS, ['userId'], Table::USERS, ['id'], 'CASCADE', null);
$this->addForeignKey(null, Table::SHUNNEDMESSAGES, ['userId'], Table::USERS, ['id'], 'CASCADE', null);
$this->addForeignKey(null, Table::SITES, ['groupId'], Table::SITEGROUPS, ['id'], 'CASCADE', null);
$this->addForeignKey(null, Table::STRUCTUREELEMENTS, ['elementId'], Table::ELEMENTS, ['id'], 'CASCADE', null);
$this->addForeignKey(null, Table::STRUCTUREELEMENTS, ['structureId'], Table::STRUCTURES, ['id'], 'CASCADE', null);
$this->addForeignKey(null, Table::TAGGROUPS, ['fieldLayoutId'], Table::FIELDLAYOUTS, ['id'], 'SET NULL', null);
$this->addForeignKey(null, Table::TAGS, ['groupId'], Table::TAGGROUPS, ['id'], 'CASCADE', null);
Expand Down
32 changes: 32 additions & 0 deletions src/migrations/m230221_185926_drop_element_fks.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace craft\migrations;

use craft\db\Migration;
use craft\db\Table;

/**
* m230221_185926_drop_element_fks migration.
*/
class m230221_185926_drop_element_fks extends Migration
{
/**
* @inheritdoc
*/
public function safeUp()
{
$this->dropForeignKeyIfExists(Table::RELATIONS, ['targetId']);
$this->dropForeignKeyIfExists(Table::STRUCTUREELEMENTS, ['elementId']);
return true;
}

/**
* @inheritdoc
*/
public function safeDown()
{
$this->addForeignKey(null, Table::RELATIONS, ['targetId'], Table::ELEMENTS, ['id'], 'CASCADE');
$this->addForeignKey(null, Table::STRUCTUREELEMENTS, ['elementId'], Table::ELEMENTS, ['id'], 'CASCADE');
return true;
}
}
54 changes: 54 additions & 0 deletions src/services/Gc.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ public function run(bool $force = false)

$this->_deleteOrphanedDraftsAndRevisions();
$this->_deleteOrphanedSearchIndexes();
$this->_deleteOrphanedRelations();
$this->_deleteOrphanedStructureElements();

// Fire a 'run' event
if ($this->hasEventHandlers(self::EVENT_RUN)) {
Expand Down Expand Up @@ -344,6 +346,58 @@ private function _deleteOrphanedSearchIndexes(): void
$this->_stdout("done\n", Console::FG_GREEN);
}

private function _deleteOrphanedRelations(): void
{
$this->_stdout(' > deleting orphaned relations ... ');
$db = Craft::$app->getDb();
$relationsTable = Table::RELATIONS;
$elementsTable = Table::ELEMENTS;

if ($db->getIsMysql()) {
$sql = <<<SQL
DELETE [[r]].* FROM $relationsTable [[r]]
LEFT JOIN $elementsTable [[e]] ON [[e.id]] = [[r.targetId]]
WHERE [[e.id]] IS NULL
SQL;
} else {
$sql = <<<SQL
DELETE FROM $relationsTable
USING $relationsTable [[r]]
LEFT JOIN $elementsTable [[e]] ON [[e.id]] = [[r.targetId]]
WHERE [[e.id]] IS NULL
SQL;
}

$db->createCommand($sql)->execute();
$this->_stdout("done\n", Console::FG_GREEN);
}

private function _deleteOrphanedStructureElements(): void
{
$this->_stdout(' > deleting orphaned structure elements ... ');
$db = Craft::$app->getDb();
$structureElementsTable = Table::STRUCTUREELEMENTS;
$elementsTable = Table::ELEMENTS;

if ($db->getIsMysql()) {
$sql = <<<SQL
DELETE [[se]].* FROM $structureElementsTable [[se]]
LEFT JOIN $elementsTable [[e]] ON [[e.id]] = [[se.elementId]]
WHERE [[se.elementId]] IS NOT NULL AND [[e.id]] IS NULL
SQL;
} else {
$sql = <<<SQL
DELETE FROM $structureElementsTable
USING $structureElementsTable [[se]]
LEFT JOIN $elementsTable [[e]] ON [[e.id]] = [[se.elementId]]
WHERE [[se.elementId]] IS NOT NULL AND [[e.id]] IS NULL
SQL;
}

$db->createCommand($sql)->execute();
$this->_stdout("done\n", Console::FG_GREEN);
}

private function _gcCache(): void
{
$cache = Craft::$app->getCache();
Expand Down

0 comments on commit fc799d9

Please sign in to comment.