Skip to content

Commit 1a50eb4

Browse files
committed
Fix backport
Signed-off-by: Git'Fellow <[email protected]>
1 parent 64469ef commit 1a50eb4

File tree

1 file changed

+44
-9
lines changed

1 file changed

+44
-9
lines changed

apps/files_sharing/lib/DeleteOrphanedSharesJob.php

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@
3030
use OCP\AppFramework\Db\TTransactional;
3131
use OCP\AppFramework\Utility\ITimeFactory;
3232
use OCP\BackgroundJob\TimedJob;
33+
use OCP\DB\QueryBuilder\IQueryBuilder;
34+
use OCP\IDBConnection;
35+
use PDO;
36+
use Psr\Log\LoggerInterface;
37+
use function array_map;
3338

3439
/**
3540
* Delete all share entries that have no matching entries in the file cache table.
@@ -69,15 +74,45 @@ public function __construct(
6974
* @param array $argument unused argument
7075
*/
7176
public function run($argument) {
72-
$connection = \OC::$server->getDatabaseConnection();
73-
$logger = \OC::$server->getLogger();
77+
$qbSelect = $this->db->getQueryBuilder();
78+
$qbSelect->select('id')
79+
->from('share', 's')
80+
->leftJoin('s', 'filecache', 'fc', $qbSelect->expr()->eq('s.file_source', 'fc.fileid'))
81+
->where($qbSelect->expr()->isNull('fc.fileid'))
82+
->setMaxResults(self::CHUNK_SIZE);
83+
$deleteQb = $this->db->getQueryBuilder();
84+
$deleteQb->delete('share')
85+
->where(
86+
$deleteQb->expr()->in('id', $deleteQb->createParameter('ids'), IQueryBuilder::PARAM_INT_ARRAY)
87+
);
7488

75-
$sql =
76-
'DELETE FROM `*PREFIX*share` ' .
77-
'WHERE `item_type` in (\'file\', \'folder\') ' .
78-
'AND NOT EXISTS (SELECT `fileid` FROM `*PREFIX*filecache` WHERE `file_source` = `fileid`)';
79-
80-
$deletedEntries = $connection->executeUpdate($sql);
81-
$logger->debug("$deletedEntries orphaned share(s) deleted", ['app' => 'DeleteOrphanedSharesJob']);
89+
/**
90+
* Read a chunk of orphan rows and delete them. Continue as long as the
91+
* chunk is filled and time before the next cron run does not run out.
92+
*
93+
* Note: With isolation level READ COMMITTED, the database will allow
94+
* other transactions to delete rows between our SELECT and DELETE. In
95+
* that (unlikely) case, our DELETE will have fewer affected rows than
96+
* IDs passed for the WHERE IN. If this happens while processing a full
97+
* chunk, the logic below will stop prematurely.
98+
* Note: The queries below are optimized for low database locking. They
99+
* could be combined into one single DELETE with join or sub query, but
100+
* that has shown to (dead)lock often.
101+
*/
102+
$cutOff = $this->time->getTime() + self::INTERVAL;
103+
do {
104+
$deleted = $this->atomic(function () use ($qbSelect, $deleteQb) {
105+
$result = $qbSelect->executeQuery();
106+
$ids = array_map('intval', $result->fetchAll(PDO::FETCH_COLUMN));
107+
$result->closeCursor();
108+
$deleteQb->setParameter('ids', $ids, IQueryBuilder::PARAM_INT_ARRAY);
109+
$deleted = $deleteQb->executeStatement();
110+
$this->logger->debug("{deleted} orphaned share(s) deleted", [
111+
'app' => 'DeleteOrphanedSharesJob',
112+
'deleted' => $deleted,
113+
]);
114+
return $deleted;
115+
}, $this->db);
116+
} while ($deleted >= self::CHUNK_SIZE && $this->time->getTime() <= $cutOff);
82117
}
83118
}

0 commit comments

Comments
 (0)