Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/Cron/Cleanup.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ protected function run($argument): void {
$removedSessions = $this->sessionService->removeInactiveSessionsWithoutSteps();
$this->logger->debug('Removed ' . $removedSessions . ' inactive sessions');

$this->logger->debug('Run cleanup job for orphaned steps');
$removedSteps = $this->sessionService->removeOrphanedSteps();
$this->logger->debug('Removed ' . $removedSteps . ' orphaned steps');

$this->logger->debug('Run cleanup job for obsolete documents folders');
$this->documentService->cleanupOldDocumentsFolders();
}
Expand Down
36 changes: 36 additions & 0 deletions lib/Db/SessionMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,42 @@ public function deleteInactiveWithoutSteps(?int $documentId = null): int {
return $deletedCount;
}

public function deleteOrphanedSteps(): int {
$startTime = microtime(true);
$maxExecutionSeconds = 30;
$batchSize = 1000;
$deletedCount = 0;

do {
$orphanedStepsBuilder = $this->db->getQueryBuilder();
$orphanedStepsBuilder->select('st.id')
->from('text_steps', 'st')
->leftJoin('st', 'text_sessions', 's', $orphanedStepsBuilder->expr()->eq('st.session_id', 's.id'))
->where($orphanedStepsBuilder->expr()->isNull('s.id'))
->setMaxResults($batchSize);

$result = $orphanedStepsBuilder->executeQuery();
$stepIds = array_map(function ($row) {
return (int)$row['id'];
}, $result->fetchAll());
$result->closeCursor();

if (empty($stepIds)) {
break;
}

$deleteBuilder = $this->db->getQueryBuilder();
$batchDeleted = $deleteBuilder->delete('text_steps')
->where($deleteBuilder->expr()->in('id', $deleteBuilder->createParameter('ids'), IQueryBuilder::PARAM_INT_ARRAY))
->setParameter('ids', $stepIds, IQueryBuilder::PARAM_INT_ARRAY)
->executeStatement();

$deletedCount += $batchDeleted;
} while ((microtime(true) - $startTime) < $maxExecutionSeconds);

return $deletedCount;
}

public function deleteByDocumentId(int $documentId): int {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->getTableName())
Expand Down
4 changes: 4 additions & 0 deletions lib/Service/SessionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ public function removeInactiveSessionsWithoutSteps(?int $documentId = null): int
return $this->sessionMapper->deleteInactiveWithoutSteps($documentId);
}

public function removeOrphanedSteps(): int {
return $this->sessionMapper->deleteOrphanedSteps();
}

public function getSession(int $documentId, int $sessionId, string $token): ?Session {
if ($this->session !== null) {
return $this->session;
Expand Down
56 changes: 56 additions & 0 deletions tests/unit/Db/SessionMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,60 @@ public function testDeleteInactiveWithoutStepsMultiple() {

self::assertCount(0, $this->sessionMapper->findAll(1));
}

public function testDeleteOrphanedSteps() {
$this->stepMapper->deleteAll(1);
$this->sessionMapper->deleteByDocumentId(1);

// Create session
$session = $this->sessionMapper->insert(Session::fromParams([
'userId' => 'admin',
'documentId' => 1,
'lastContact' => time(),
'token' => uniqid(),
'color' => '00ff00',
]));

// Create steps for session
$this->stepMapper->insert(Step::fromParams([
'sessionId' => $session->getId(),
'documentId' => 1,
'data' => 'YJSDATA1',
'version' => 1,
]));
$this->stepMapper->insert(Step::fromParams([
'sessionId' => $session->getId(),
'documentId' => 1,
'data' => 'YJSDATA2',
'version' => 2,
]));

// Create orphaned steps
$this->stepMapper->insert(Step::fromParams([
'sessionId' => 99999, // Non-existent session
'documentId' => 1,
'data' => 'ORPHANED1',
'version' => 3,
]));
$this->stepMapper->insert(Step::fromParams([
'sessionId' => 99998, // Non-existent session
'documentId' => 1,
'data' => 'ORPHANED2',
'version' => 4,
]));

// Verify 4 steps total
$allSteps = $this->stepMapper->find(1, 0);
self::assertCount(4, $allSteps);

// Delete orphaned steps
$deletedCount = $this->sessionMapper->deleteOrphanedSteps();

// Should have deleted 2 orphaned steps
self::assertEquals(2, $deletedCount);

// Should have 2 valid steps remaining
$remainingSteps = $this->stepMapper->find(1, 0);
self::assertCount(2, $remainingSteps);
}
}
Loading