Skip to content
Prev Previous commit
Next Next commit
Only delete sessions with only steps below lastSavedVersion
Signed-off-by: Benjamin Frueh <[email protected]>
  • Loading branch information
benjaminfrueh committed Sep 22, 2025
commit 1dc1cba7c69fabb4bee160fff8de8291bd71fc29
31 changes: 21 additions & 10 deletions lib/Db/SessionMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,21 @@ public function deleteOldSessions(int $ageInSeconds): int {
$ageThreshold = time() - $ageInSeconds;

do {
$oldSessionsBuilder = $this->db->getQueryBuilder();
$result = $oldSessionsBuilder->select('id')
->from('text_sessions')
->where($oldSessionsBuilder->expr()->lt('last_contact', $oldSessionsBuilder->createNamedParameter($ageThreshold)))
$oldSessionsQb = $this->db->getQueryBuilder();
$result = $oldSessionsQb->select('s.id')
->from('text_sessions', 's')
->leftJoin('s', 'text_documents', 'd', $oldSessionsQb->expr()->eq('s.document_id', 'd.id'))
->leftJoin('s', 'text_steps', 'st', $oldSessionsQb->expr()->andX(
$oldSessionsQb->expr()->eq('st.session_id', 's.id'),
$oldSessionsQb->expr()->gte('st.id', 'd.last_saved_version')
))
->where($oldSessionsQb->expr()->lt('s.last_contact', $oldSessionsQb->createNamedParameter($ageThreshold)))
->andWhere(
$oldSessionsQb->expr()->orX(
$oldSessionsQb->expr()->isNull('d.id'),
$oldSessionsQb->expr()->isNull('st.id')
)
)
->setMaxResults($batchSize)
->executeQuery();

Expand All @@ -166,15 +177,15 @@ public function deleteOldSessions(int $ageInSeconds): int {
break;
}

$deleteStepsBuilder = $this->db->getQueryBuilder();
$deleteStepsBuilder->delete('text_steps')
->where($deleteStepsBuilder->expr()->in('session_id', $deleteStepsBuilder->createParameter('ids'), IQueryBuilder::PARAM_INT_ARRAY))
$deleteStepsQb = $this->db->getQueryBuilder();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This delete query should be removed. As explained earlier, we don't want to delete the steps from sessions that got deleted. This task should really merely remove sessions older than 90 days as they will be recreated easily without data loss. Removing steps more or less unconditionally on the other hand might result in data loss.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, in the recent commit I removed the step deletion part from the deleteOldSessions function.

$deleteStepsQb->delete('text_steps')
->where($deleteStepsQb->expr()->in('session_id', $deleteStepsQb->createParameter('ids'), IQueryBuilder::PARAM_INT_ARRAY))
->setParameter('ids', $sessionIds, IQueryBuilder::PARAM_INT_ARRAY)
->executeStatement();

$deleteSessionsBuilder = $this->db->getQueryBuilder();
$batchDeleted = $deleteSessionsBuilder->delete('text_sessions')
->where($deleteSessionsBuilder->expr()->in('id', $deleteSessionsBuilder->createParameter('ids'), IQueryBuilder::PARAM_INT_ARRAY))
$deleteSessionsQb = $this->db->getQueryBuilder();
$batchDeleted = $deleteSessionsQb->delete('text_sessions')
->where($deleteSessionsQb->expr()->in('id', $deleteSessionsQb->createParameter('ids'), IQueryBuilder::PARAM_INT_ARRAY))
->setParameter('ids', $sessionIds, IQueryBuilder::PARAM_INT_ARRAY)
->executeStatement();

Expand Down
57 changes: 43 additions & 14 deletions tests/unit/Db/SessionMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ class SessionMapperTest extends \Test\TestCase {

private SessionMapper $sessionMapper;
private StepMapper $stepMapper;
private DocumentMapper $documentMapper;

public function setUp(): void {
parent::setUp();
$this->sessionMapper = \OCP\Server::get(SessionMapper::class);
$this->stepMapper = \OCP\Server::get(StepMapper::class);

$this->documentMapper = \OCP\Server::get(DocumentMapper::class);
}

public function testDeleteInactiveWithoutSteps() {
Expand Down Expand Up @@ -100,59 +101,87 @@ public function testDeleteInactiveWithoutStepsMultiple() {
}

public function testDeleteOldSessions() {
$this->stepMapper->deleteAll(1);
$this->sessionMapper->deleteByDocumentId(1);
$this->documentMapper->clearAll();
$this->sessionMapper->clearAll();
$this->stepMapper->clearAll();

$fourMonthsAgo = time() - (120 * 24 * 60 * 60);
$oneWeekAgo = time() - (7 * 24 * 60 * 60);

// Create old and recent session
// Create document
$document = $this->documentMapper->insert(Document::fromParams([
'id' => 1,
'currentVersion' => 0,
'lastSavedVersion' => 100,
'lastSavedVersionTime' => time()
]));

// Create sessions
$oldSession = $this->sessionMapper->insert(Session::fromParams([
'id' => 1,
'userId' => 'admin',
'documentId' => 1,
'documentId' => $document->getId(),
'lastContact' => $fourMonthsAgo,
'token' => uniqid(),
'color' => '00ff00',
]));
$oldSessionWithNewVersion = $this->sessionMapper->insert(Session::fromParams([
'userId' => 'admin',
'documentId' => $document->getId(),
'lastContact' => $fourMonthsAgo,
'token' => uniqid(),
'color' => '00ff00',
]));
$recentSession = $this->sessionMapper->insert(Session::fromParams([
'userId' => 'admin',
'documentId' => 1,
'documentId' => $document->getId(),
'lastContact' => $oneWeekAgo,
'token' => uniqid(),
'color' => 'ff0000',
]));

// Create steps for old and recent session
$this->stepMapper->insert(Step::fromParams([
'id' => 1,
'sessionId' => $oldSession->getId(),
'documentId' => 1,
'data' => 'OLD_STEP',
'version' => 1,
]));
$this->stepMapper->insert(Step::fromParams([
'id' => 101,
'sessionId' => $oldSessionWithNewVersion->getId(),
'documentId' => 1,
'data' => 'OLD_STEP_NEW_VERSION',
'version' => 1,
]));
$this->stepMapper->insert(Step::fromParams([
'id' => 2,
'sessionId' => $recentSession->getId(),
'documentId' => 1,
'data' => 'RECENT_STEP',
'version' => 2,
]));

// Verify 2 sessions and 2 steps
self::assertCount(2, $this->sessionMapper->findAll(1));
self::assertCount(2, $this->stepMapper->find(1, 0));
// Verify 3 sessions and 3 steps
self::assertCount(3, $this->sessionMapper->findAll(1));
self::assertCount(3, $this->stepMapper->find(1, 0));

// Delete sessions older than 90 days
$threeMonths = 90 * 24 * 60 * 60;
$deletedCount = $this->sessionMapper->deleteOldSessions($threeMonths);
self::assertEquals(1, $deletedCount);

// Should have 1 recent session remaining
// Should have 2 sessions remaining
$remainingSessions = $this->sessionMapper->findAll(1);
self::assertCount(1, $remainingSessions);
self::assertEquals($recentSession->getId(), $remainingSessions[0]->getId());
self::assertCount(2, $remainingSessions);
self::assertEquals($oldSessionWithNewVersion->getId(), $remainingSessions[0]->getId());
self::assertEquals($recentSession->getId(), $remainingSessions[1]->getId());

// Should have 1 step from recent session remaining
// Should have 2 step remaining
$remainingSteps = $this->stepMapper->find(1, 0);
self::assertCount(1, $remainingSteps);
self::assertCount(2, $remainingSteps);
self::assertEquals($recentSession->getId(), $remainingSteps[0]->getSessionId());
self::assertEquals($oldSessionWithNewVersion->getId(), $remainingSteps[1]->getSessionId());
}
}
Loading