fix(query-builder): Don't catch UniqueConstraintViolationException

UniqueConstraintViolationException is no longer throw directly but
instead is now wrapped inside a \OCP\DB\Exception. So check the
exception reason.

Signed-off-by: Carl Schwan <carl.schwan@nextclound.com>
pull/54664/head
Carl Schwan 2025-08-26 14:40:39 +07:00 committed by Carl Schwan
parent c21b8169ff
commit c4e6fbdae7
8 changed files with 66 additions and 36 deletions

@ -7,7 +7,6 @@ declare(strict_types=1);
*/ */
namespace OC\Authentication\Token; namespace OC\Authentication\Token;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OC\Authentication\Exceptions\InvalidTokenException as OcInvalidTokenException; use OC\Authentication\Exceptions\InvalidTokenException as OcInvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException; use OC\Authentication\Exceptions\PasswordlessTokenException;
use OCP\Authentication\Exceptions\ExpiredTokenException; use OCP\Authentication\Exceptions\ExpiredTokenException;
@ -15,6 +14,7 @@ use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\Authentication\Exceptions\WipeTokenException; use OCP\Authentication\Exceptions\WipeTokenException;
use OCP\Authentication\Token\IProvider as OCPIProvider; use OCP\Authentication\Token\IProvider as OCPIProvider;
use OCP\Authentication\Token\IToken as OCPIToken; use OCP\Authentication\Token\IToken as OCPIToken;
use OCP\DB\Exception;
class Manager implements IProvider, OCPIProvider { class Manager implements IProvider, OCPIProvider {
/** @var PublicKeyTokenProvider */ /** @var PublicKeyTokenProvider */
@ -60,7 +60,10 @@ class Manager implements IProvider, OCPIProvider {
$remember, $remember,
$scope, $scope,
); );
} catch (UniqueConstraintViolationException $e) { } catch (Exception $e) {
if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
throw $e;
}
// It's rare, but if two requests of the same session (e.g. env-based SAML) // It's rare, but if two requests of the same session (e.g. env-based SAML)
// try to create the session token they might end up here at the same time // try to create the session token they might end up here at the same time
// because we use the session ID as token and the db token is created anew // because we use the session ID as token and the db token is created anew

@ -8,7 +8,6 @@ declare(strict_types=1);
*/ */
namespace OC\Collaboration\Resources; namespace OC\Collaboration\Resources;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OCP\Collaboration\Resources\CollectionException; use OCP\Collaboration\Resources\CollectionException;
use OCP\Collaboration\Resources\ICollection; use OCP\Collaboration\Resources\ICollection;
use OCP\Collaboration\Resources\IManager; use OCP\Collaboration\Resources\IManager;
@ -16,6 +15,7 @@ use OCP\Collaboration\Resources\IProvider;
use OCP\Collaboration\Resources\IProviderManager; use OCP\Collaboration\Resources\IProviderManager;
use OCP\Collaboration\Resources\IResource; use OCP\Collaboration\Resources\IResource;
use OCP\Collaboration\Resources\ResourceException; use OCP\Collaboration\Resources\ResourceException;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection; use OCP\IDBConnection;
use OCP\IUser; use OCP\IUser;
@ -351,7 +351,10 @@ class Manager implements IManager {
]); ]);
try { try {
$query->executeStatement(); $query->executeStatement();
} catch (UniqueConstraintViolationException $e) { } catch (Exception $e) {
if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
throw $e;
}
} }
} }
@ -367,7 +370,10 @@ class Manager implements IManager {
]); ]);
try { try {
$query->executeStatement(); $query->executeStatement();
} catch (UniqueConstraintViolationException $e) { } catch (Exception $e) {
if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
throw $e;
}
} }
} }

@ -7,7 +7,6 @@
*/ */
namespace OC\Files\Cache; namespace OC\Files\Cache;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OC\DB\Exceptions\DbalException; use OC\DB\Exceptions\DbalException;
use OC\DB\QueryBuilder\Sharded\ShardDefinition; use OC\DB\QueryBuilder\Sharded\ShardDefinition;
use OC\Files\Cache\Wrapper\CacheJail; use OC\Files\Cache\Wrapper\CacheJail;
@ -16,6 +15,7 @@ use OC\Files\Search\SearchComparison;
use OC\Files\Search\SearchQuery; use OC\Files\Search\SearchQuery;
use OC\Files\Storage\Wrapper\Encryption; use OC\Files\Storage\Wrapper\Encryption;
use OC\SystemConfig; use OC\SystemConfig;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Cache\CacheEntryInsertedEvent; use OCP\Files\Cache\CacheEntryInsertedEvent;
@ -249,7 +249,7 @@ class Cache implements ICache {
* @param array $data * @param array $data
* *
* @return int file id * @return int file id
* @throws \RuntimeException * @throws \RuntimeException|Exception
*/ */
public function insert($file, array $data) { public function insert($file, array $data) {
// normalize file // normalize file
@ -309,15 +309,19 @@ class Cache implements ICache {
$this->eventDispatcher->dispatchTyped($event); $this->eventDispatcher->dispatchTyped($event);
return $fileId; return $fileId;
} }
} catch (UniqueConstraintViolationException $e) { } catch (Exception $e) {
// entry exists already if ($e->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
if ($this->connection->inTransaction()) { // entry exists already
$this->connection->commit(); if ($this->connection->inTransaction()) {
$this->connection->beginTransaction(); $this->connection->commit();
$this->connection->beginTransaction();
}
} else {
throw $e;
} }
} }
// The file was created in the mean time // The file was created in the meantime
if (($id = $this->getId($file)) > -1) { if (($id = $this->getId($file)) > -1) {
$this->update($id, $data); $this->update($id, $data);
return $id; return $id;
@ -377,7 +381,10 @@ class Cache implements ICache {
} }
$query->executeStatement(); $query->executeStatement();
} catch (UniqueConstraintViolationException $e) { } catch (Exception $e) {
if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
throw $e;
}
$query = $this->getQueryBuilder(); $query = $this->getQueryBuilder();
$query->update('filecache_extended') $query->update('filecache_extended')
->whereFileId($id) ->whereFileId($id)

@ -7,8 +7,8 @@
*/ */
namespace OC\Group; namespace OC\Group;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OC\User\LazyUser; use OC\User\LazyUser;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Group\Backend\ABackend; use OCP\Group\Backend\ABackend;
use OCP\Group\Backend\IAddToGroupBackend; use OCP\Group\Backend\IAddToGroupBackend;
@ -75,8 +75,12 @@ class Database extends ABackend implements
->setValue('gid', $builder->createNamedParameter($gid)) ->setValue('gid', $builder->createNamedParameter($gid))
->setValue('displayname', $builder->createNamedParameter($name)) ->setValue('displayname', $builder->createNamedParameter($name))
->executeStatement(); ->executeStatement();
} catch (UniqueConstraintViolationException $e) { } catch (Exception $e) {
return null; if ($e->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
return null;
} else {
throw $e;
}
} }
// Add to cache // Add to cache

@ -8,7 +8,7 @@ declare(strict_types=1);
*/ */
namespace OC\SystemTag; namespace OC\SystemTag;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventDispatcher;
use OCP\IAppConfig; use OCP\IAppConfig;
@ -177,12 +177,15 @@ class SystemTagManager implements ISystemTagManager {
try { try {
$query->executeStatement(); $query->executeStatement();
} catch (UniqueConstraintViolationException $e) { } catch (Exception $e) {
throw new TagAlreadyExistsException( if ($e->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
'Tag ("' . $truncatedTagName . '", ' . $userVisible . ', ' . $userAssignable . ') already exists', throw new TagAlreadyExistsException(
0, 'Tag ("' . $truncatedTagName . '", ' . $userVisible . ', ' . $userAssignable . ') already exists',
$e 0,
); $e
);
}
throw $e;
} }
$tagId = $query->getLastInsertId(); $tagId = $query->getLastInsertId();
@ -262,12 +265,15 @@ class SystemTagManager implements ISystemTagManager {
'Tag does not exist', 0, null, [$tagId] 'Tag does not exist', 0, null, [$tagId]
); );
} }
} catch (UniqueConstraintViolationException $e) { } catch (Exception $e) {
throw new TagAlreadyExistsException( if ($e->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
'Tag ("' . $newName . '", ' . $userVisible . ', ' . $userAssignable . ') already exists', throw new TagAlreadyExistsException(
0, 'Tag ("' . $newName . '", ' . $userVisible . ', ' . $userAssignable . ') already exists',
$e 0,
); $e
);
}
throw $e;
} }
$this->dispatcher->dispatch(ManagerEvent::EVENT_UPDATE, new ManagerEvent( $this->dispatcher->dispatch(ManagerEvent::EVENT_UPDATE, new ManagerEvent(

@ -9,7 +9,7 @@ declare(strict_types=1);
*/ */
namespace OC\SystemTag; namespace OC\SystemTag;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventDispatcher;
use OCP\IDBConnection; use OCP\IDBConnection;
@ -151,7 +151,10 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper {
$query->setParameter('tagid', $tagId); $query->setParameter('tagid', $tagId);
$query->executeStatement(); $query->executeStatement();
$tagsAssigned[] = $tagId; $tagsAssigned[] = $tagId;
} catch (UniqueConstraintViolationException $e) { } catch (Exception $e) {
if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
throw $e;
}
// ignore existing relations // ignore existing relations
} }
} }

@ -146,7 +146,7 @@ interface IDBConnection {
* @return int number of inserted rows * @return int number of inserted rows
* @throws Exception used to be the removed dbal exception, since 21.0.0 it's \OCP\DB\Exception * @throws Exception used to be the removed dbal exception, since 21.0.0 it's \OCP\DB\Exception
* @since 6.0.0 - parameter $compare was added in 8.1.0, return type changed from boolean in 8.1.0 * @since 6.0.0 - parameter $compare was added in 8.1.0, return type changed from boolean in 8.1.0
* @deprecated 15.0.0 - use unique index and "try { $db->insert() } catch (UniqueConstraintViolationException $e) {}" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371 * @deprecated 15.0.0 - use unique index and "try { $db->insert() } catch (\OCP\DB\Exception $e) { if ($e->getReason() === \OCP\DB\Exception::REASON_CONSTRAINT_VIOLATION) {} }" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371
*/ */
public function insertIfNotExist(string $table, array $input, ?array $compare = null); public function insertIfNotExist(string $table, array $input, ?array $compare = null);

@ -9,12 +9,12 @@ declare(strict_types=1);
namespace Test\Authentication\Token; namespace Test\Authentication\Token;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Token\IToken; use OC\Authentication\Token\IToken;
use OC\Authentication\Token\Manager; use OC\Authentication\Token\Manager;
use OC\Authentication\Token\PublicKeyToken; use OC\Authentication\Token\PublicKeyToken;
use OC\Authentication\Token\PublicKeyTokenProvider; use OC\Authentication\Token\PublicKeyTokenProvider;
use OCP\DB\Exception;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase; use Test\TestCase;
@ -62,8 +62,9 @@ class ManagerTest extends TestCase {
} }
public function testGenerateConflictingToken(): void { public function testGenerateConflictingToken(): void {
/** @var MockObject|UniqueConstraintViolationException $exception */ /** @var MockObject|Exception $exception */
$exception = $this->createMock(UniqueConstraintViolationException::class); $exception = $this->createMock(Exception::class);
$exception->method('getReason')->willReturn(Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION);
$token = new PublicKeyToken(); $token = new PublicKeyToken();
$token->setUid('uid'); $token->setUid('uid');