diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index e5dd89c5b..e2a1c234a 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -12,6 +12,7 @@ use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Exception\Timeout as TimeoutException; use Utopia\Database\Exception\Truncate as TruncateException; +use Utopia\Database\Exception\Unique as UniqueException; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; @@ -816,6 +817,7 @@ public function deleteIndex(string $collection, string $id): bool * @return Document * @throws Exception * @throws PDOException + * @throws UniqueException * @throws DuplicateException * @throws \Throwable */ @@ -942,6 +944,7 @@ public function createDocument(Document $collection, Document $document): Docume * @return Document * @throws Exception * @throws PDOException + * @throws UniqueException * @throws DuplicateException * @throws \Throwable */ @@ -1795,7 +1798,11 @@ protected function processException(PDOException $e): \Exception // Duplicate row if ($e->getCode() === '23000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1062) { - return new DuplicateException('Document already exists', $e->getCode(), $e); + if (str_ends_with($e->getMessage(), "._uid'") || str_ends_with($e->getMessage(), "'_uid'")) { + return new DuplicateException('Document already exists', $e->getCode(), $e); + } + + return new UniqueException('Document already exists', $e->getCode(), $e); } // Data is too big for column resize diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index d8cd83f4e..e103a530e 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -13,6 +13,7 @@ use Utopia\Database\Exception\Timeout as TimeoutException; use Utopia\Database\Exception\Transaction as TransactionException; use Utopia\Database\Exception\Truncate as TruncateException; +use Utopia\Database\Exception\Unique as UniqueException; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; @@ -1916,7 +1917,16 @@ protected function processException(PDOException $e): \Exception // Duplicate row if ($e->getCode() === '23505' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 7) { - return new DuplicateException('Document already exists', $e->getCode(), $e); + if (preg_match('/Key \(([^)]+)\)=\(.+\) already exists/', $e->getMessage(), $matches)) { + $columns = array_map('trim', explode(',', $matches[1])); + sort($columns); + $target = $this->sharedTables ? ['_tenant', '_uid'] : ['_uid']; + if ($columns == $target) { + return new DuplicateException('Document already exists', $e->getCode(), $e); + } + } + + return new UniqueException('Document already exists', $e->getCode(), $e); } // Data is too big for column resize diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index a892b6626..c465acec0 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -8,11 +8,11 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception as DatabaseException; -use Utopia\Database\Exception\Duplicate; use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\NotFound as NotFoundException; use Utopia\Database\Exception\Timeout as TimeoutException; use Utopia\Database\Exception\Transaction as TransactionException; +use Utopia\Database\Exception\Unique as UniqueException; use Utopia\Database\Helpers\ID; /** @@ -517,7 +517,7 @@ public function deleteIndex(string $collection, string $id): bool * @return Document * @throws Exception * @throws PDOException - * @throws Duplicate + * @throws UniqueException */ public function createDocument(Document $collection, Document $document): Document { @@ -619,10 +619,7 @@ public function createDocument(Document $collection, Document $document): Docume $stmtPermissions->execute(); } } catch (PDOException $e) { - throw match ($e->getCode()) { - "1062", "23000" => new Duplicate('Duplicated document: ' . $e->getMessage()), - default => $e, - }; + throw $this->processException($e); } @@ -639,7 +636,7 @@ public function createDocument(Document $collection, Document $document): Docume * @return Document * @throws Exception * @throws PDOException - * @throws Duplicate + * @throws UniqueException */ public function updateDocument(Document $collection, string $id, Document $document, bool $skipPermissions): Document { @@ -841,11 +838,7 @@ public function updateDocument(Document $collection, string $id, Document $docum $stmtAddPermissions->execute(); } } catch (PDOException $e) { - throw match ($e->getCode()) { - '1062', - '23000' => new Duplicate('Duplicated document: ' . $e->getMessage()), - default => $e, - }; + throw $this->processException($e); } return $document; @@ -1248,9 +1241,21 @@ protected function processException(PDOException $e): \Exception return new TimeoutException('Query timed out', $e->getCode(), $e); } - // Duplicate - if ($e->getCode() === 'HY000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1) { - return new DuplicateException('Document already exists', $e->getCode(), $e); + // Duplicate row + if ($e->getCode() === '23000' && ($e->errorInfo[1] ?? null) === 19) { + $msg = $e->errorInfo[2] ?? $e->getMessage(); + + // Match all table.column pairs (handles commas & spaces) + if (preg_match_all('/\b([^.]+)\.([^\s,]+)/', $msg, $matches, PREG_SET_ORDER)) { + $columns = array_map(fn ($m) => $m[2], $matches); + sort($columns); + + if ($columns === ['_tenant', '_uid'] || in_array('_uid', $columns)) { + return new DuplicateException('Document already exists', $e->getCode(), $e); + } + } + + return new UniqueException('Document already exists', $e->getCode(), $e); } return $e; diff --git a/src/Database/Exception/Unique.php b/src/Database/Exception/Unique.php new file mode 100644 index 000000000..d8e2fe501 --- /dev/null +++ b/src/Database/Exception/Unique.php @@ -0,0 +1,7 @@ +fail('Failed to throw exception'); } catch (Throwable $e) { $this->assertInstanceOf(DuplicateException::class, $e); + $this->assertInstanceOf(UniqueException::class, $e); } } /** @@ -4804,6 +4806,7 @@ public function testUniqueIndexDuplicateUpdate(): void $this->fail('Failed to throw exception'); } catch (Throwable $e) { $this->assertInstanceOf(DuplicateException::class, $e); + $this->assertInstanceOf(UniqueException::class, $e); } } @@ -5316,6 +5319,7 @@ public function testExceptionDuplicate(Document $document): void $this->fail('Failed to throw exception'); } catch (Throwable $e) { $this->assertInstanceOf(DuplicateException::class, $e); + $this->assertNotInstanceOf(UniqueException::class, $e); } } @@ -5339,6 +5343,7 @@ public function testExceptionCaseInsensitiveDuplicate(Document $document): Docum $this->fail('Failed to throw exception'); } catch (Throwable $e) { $this->assertInstanceOf(DuplicateException::class, $e); + $this->assertNotInstanceOf(UniqueException::class, $e); } return $document;