0
0
Fork 0
mirror of https://github.com/nextcloud/server.git synced 2025-05-20 13:10:13 +00:00
nextcloud_server/apps/dav/lib/BackgroundJob/CleanupOrphanedChildrenJob.php
Richard Steinmetz d0fcb7e638
fix(dav): move orphan cleaning logic to a chunked background job
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
2025-05-05 12:13:09 +02:00

89 lines
2.9 KiB
PHP

<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\DAV\BackgroundJob;
use OCA\DAV\CalDAV\CalDavBackend;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
use OCP\BackgroundJob\QueuedJob;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use Psr\Log\LoggerInterface;
class CleanupOrphanedChildrenJob extends QueuedJob {
public const ARGUMENT_CHILD_TABLE = 'childTable';
public const ARGUMENT_PARENT_TABLE = 'parentTable';
public const ARGUMENT_PARENT_ID = 'parentId';
public const ARGUMENT_LOG_MESSAGE = 'logMessage';
private const BATCH_SIZE = 1000;
public function __construct(
ITimeFactory $time,
private readonly IDBConnection $connection,
private readonly LoggerInterface $logger,
private readonly IJobList $jobList,
) {
parent::__construct($time);
}
protected function run($argument): void {
$childTable = $argument[self::ARGUMENT_CHILD_TABLE];
$parentTable = $argument[self::ARGUMENT_PARENT_TABLE];
$parentId = $argument[self::ARGUMENT_PARENT_ID];
$logMessage = $argument[self::ARGUMENT_LOG_MESSAGE];
$orphanCount = $this->cleanUpOrphans($childTable, $parentTable, $parentId);
$this->logger->debug(sprintf($logMessage, $orphanCount));
// Requeue if there might be more orphans
if ($orphanCount >= self::BATCH_SIZE) {
$this->jobList->add(self::class, $argument);
}
}
private function cleanUpOrphans(
string $childTable,
string $parentTable,
string $parentId,
): int {
// We can't merge both queries into a single one here as DELETEing from a table while
// SELECTing it in a sub query is not supported by Oracle DB.
// Ref https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/delete.html#idm46006185488144
$selectQb = $this->connection->getQueryBuilder();
$selectQb->select('c.id')
->from($childTable, 'c')
->leftJoin('c', $parentTable, 'p', $selectQb->expr()->eq('c.' . $parentId, 'p.id'))
->where($selectQb->expr()->isNull('p.id'))
->setMaxResults(self::BATCH_SIZE);
if (\in_array($parentTable, ['calendars', 'calendarsubscriptions'], true)) {
$calendarType = $parentTable === 'calendarsubscriptions' ? CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION : CalDavBackend::CALENDAR_TYPE_CALENDAR;
$selectQb->andWhere($selectQb->expr()->eq('c.calendartype', $selectQb->createNamedParameter($calendarType, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT));
}
$result = $selectQb->executeQuery();
$rows = $result->fetchAll();
$result->closeCursor();
if (empty($rows)) {
return 0;
}
$orphanItems = array_map(static fn ($row) => $row['id'], $rows);
$deleteQb = $this->connection->getQueryBuilder();
$deleteQb->delete($childTable)
->where($deleteQb->expr()->in('id', $deleteQb->createNamedParameter($orphanItems, IQueryBuilder::PARAM_INT_ARRAY)));
$deleteQb->executeStatement();
return count($orphanItems);
}
}