Skip to content
Open
9 changes: 8 additions & 1 deletion src/Database/Adapter/MariaDB.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -816,6 +817,7 @@ public function deleteIndex(string $collection, string $id): bool
* @return Document
* @throws Exception
* @throws PDOException
* @throws UniqueException
* @throws DuplicateException
* @throws \Throwable
*/
Expand Down Expand Up @@ -942,6 +944,7 @@ public function createDocument(Document $collection, Document $document): Docume
* @return Document
* @throws Exception
* @throws PDOException
* @throws UniqueException
* @throws DuplicateException
* @throws \Throwable
*/
Expand Down Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion src/Database/Adapter/Postgres.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down
35 changes: 20 additions & 15 deletions src/Database/Adapter/SQLite.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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);
}


Expand All @@ -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
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions src/Database/Exception/Unique.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Utopia\Database\Exception;

class Unique extends Duplicate
{
}
5 changes: 5 additions & 0 deletions tests/e2e/Adapter/Scopes/DocumentTests.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Utopia\Database\Exception\Limit as LimitException;
use Utopia\Database\Exception\Structure as StructureException;
use Utopia\Database\Exception\Type as TypeException;
use Utopia\Database\Exception\Unique as UniqueException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
Expand Down Expand Up @@ -4762,6 +4763,7 @@ public function testUniqueIndexDuplicate(): void
$this->fail('Failed to throw exception');
} catch (Throwable $e) {
$this->assertInstanceOf(DuplicateException::class, $e);
$this->assertInstanceOf(UniqueException::class, $e);
}
}
/**
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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;
Expand Down