From 73b02f7c053b8b36c2815d680ead1297f0fed66a Mon Sep 17 00:00:00 2001 From: provokateurin Date: Tue, 9 Dec 2025 11:19:03 +0100 Subject: [PATCH 1/2] feat(IDBConnection): Add getTransactionIsolation and setTransactionIsolation Signed-off-by: provokateurin --- lib/private/DB/ConnectionAdapter.php | 17 +++++++++++++++++ lib/public/IDBConnection.php | 22 ++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/lib/private/DB/ConnectionAdapter.php b/lib/private/DB/ConnectionAdapter.php index d9ccb3c54f219..4afd1f2e2f526 100644 --- a/lib/private/DB/ConnectionAdapter.php +++ b/lib/private/DB/ConnectionAdapter.php @@ -26,6 +26,13 @@ class ConnectionAdapter implements IDBConnection { /** @var Connection */ private $inner; + /** + * The currently active transaction isolation level or NULL before it has been determined. + * + * @var \Doctrine\DBAL\TransactionIsolationLevel::*|null + */ + private ?int $transactionIsolationLevel = null; + public function __construct(Connection $inner) { $this->inner = $inner; } @@ -262,4 +269,14 @@ public function getShardDefinition(string $name): ?ShardDefinition { public function getCrossShardMoveHelper(): CrossShardMoveHelper { return $this->inner->getCrossShardMoveHelper(); } + + public function setTransactionIsolation(int $level): int { + $this->transactionIsolationLevel = $level; + + return $this->executeStatement($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level)); + } + + public function getTransactionIsolation(): int { + return $this->transactionIsolationLevel ??= $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel(); + } } diff --git a/lib/public/IDBConnection.php b/lib/public/IDBConnection.php index 05ac0da2d7a2e..a1418afd06819 100644 --- a/lib/public/IDBConnection.php +++ b/lib/public/IDBConnection.php @@ -388,4 +388,26 @@ public function getShardDefinition(string $name): ?ShardDefinition; * @since 30.0.0 */ public function getCrossShardMoveHelper(): CrossShardMoveHelper; + + /** + * Sets the transaction isolation level. + * + * @param \Doctrine\DBAL\TransactionIsolationLevel::* $level The level to set. + * + * @throws \Doctrine\DBAL\Exception + * + * @since 33.0.0 + */ + public function setTransactionIsolation(int $level): int; + + /** + * Gets the currently active transaction isolation level. + * + * @return \Doctrine\DBAL\TransactionIsolationLevel::* The current transaction isolation level. + * + * @throws \Doctrine\DBAL\Exception + * + * @since 33.0.0 + */ + public function getTransactionIsolation(): int; } From 8fac473324742006d7c4d08a57cb76661271cbad Mon Sep 17 00:00:00 2001 From: provokateurin Date: Tue, 9 Dec 2025 10:54:06 +0100 Subject: [PATCH 2/2] fix(UserMountCache): Set transaction isolation level to REPEATABLE_READ before executing insertIfNotExist without unique index Signed-off-by: provokateurin --- lib/private/Files/Config/UserMountCache.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php index 7cffc01b6f281..52b1aa85c3cd0 100644 --- a/lib/private/Files/Config/UserMountCache.php +++ b/lib/private/Files/Config/UserMountCache.php @@ -7,6 +7,7 @@ */ namespace OC\Files\Config; +use Doctrine\DBAL\TransactionIsolationLevel; use OC\User\LazyUser; use OCP\Cache\CappedMemoryCache; use OCP\DB\QueryBuilder\IQueryBuilder; @@ -99,6 +100,8 @@ public function registerMounts(IUser $user, array $mounts, ?array $mountProvider $changedMounts = $this->findChangedMounts($newMounts, $cachedMounts); if ($addedMounts || $removedMounts || $changedMounts) { + $this->connection->setTransactionIsolation(TransactionIsolationLevel::REPEATABLE_READ); + $this->connection->beginTransaction(); $userUID = $user->getUID(); try { @@ -124,6 +127,8 @@ public function registerMounts(IUser $user, array $mounts, ?array $mountProvider } catch (\Throwable $e) { $this->connection->rollBack(); throw $e; + } finally { + $this->connection->setTransactionIsolation(TransactionIsolationLevel::READ_COMMITTED); } // Only fire events after all mounts have already been adjusted in the database.