From f87ec3dacf3df65808248ae82cc120c76036c6ee Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 12 Oct 2025 09:25:49 +0300 Subject: [PATCH 01/13] UniqueException --- src/Database/Adapter/MariaDB.php | 3 ++- src/Database/Adapter/Postgres.php | 3 ++- src/Database/Exception/Unique.php | 9 +++++++++ tests/e2e/Adapter/Scopes/DocumentTests.php | 9 +++++---- tests/e2e/Adapter/Scopes/Relationships/OneToOneTests.php | 5 +++-- 5 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 src/Database/Exception/Unique.php diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index e5dd89c5b..8ed9adaf3 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; @@ -1795,7 +1796,7 @@ 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); + 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 fe8214e9c..b36e207ce 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; @@ -1924,7 +1925,7 @@ 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); + return new UniqueException('Document already exists', $e->getCode(), $e); } // Data is too big for column resize diff --git a/src/Database/Exception/Unique.php b/src/Database/Exception/Unique.php new file mode 100644 index 000000000..57a4dc961 --- /dev/null +++ b/src/Database/Exception/Unique.php @@ -0,0 +1,9 @@ +expectException(DuplicateException::class); + $this->expectException(UniqueException::class); /** @var Database $database */ $database = static::getDatabase(); @@ -4794,7 +4795,7 @@ public function testUniqueIndexDuplicateUpdate(): void 'with-dash' => 'Works4' ])); - $this->expectException(DuplicateException::class); + $this->expectException(UniqueException::class); $database->updateDocument('movies', $document->getId(), $document->setAttribute('name', 'Frozen')); } @@ -5299,7 +5300,7 @@ public function testExceptionDuplicate(Document $document): void $document->setAttribute('$id', 'duplicated'); $database->createDocument($document->getCollection(), $document); - $this->expectException(DuplicateException::class); + $this->expectException(UniqueException::class); $database->createDocument($document->getCollection(), $document); } @@ -5317,7 +5318,7 @@ public function testExceptionCaseInsensitiveDuplicate(Document $document): Docum $document->setAttribute('$id', 'CaseSensitive'); - $this->expectException(DuplicateException::class); + $this->expectException(UniqueException::class); $database->createDocument($document->getCollection(), $document); return $document; diff --git a/tests/e2e/Adapter/Scopes/Relationships/OneToOneTests.php b/tests/e2e/Adapter/Scopes/Relationships/OneToOneTests.php index 52881707b..723647998 100644 --- a/tests/e2e/Adapter/Scopes/Relationships/OneToOneTests.php +++ b/tests/e2e/Adapter/Scopes/Relationships/OneToOneTests.php @@ -10,6 +10,7 @@ use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\Restricted as RestrictedException; use Utopia\Database\Exception\Structure as StructureException; +use Utopia\Database\Exception\Unique as UniqueException; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; @@ -292,7 +293,7 @@ public function testOneToOneOneWayRelationship(): void ); $this->fail('Failed to throw duplicate exception'); } catch (Exception $e) { - $this->assertInstanceOf(DuplicateException::class, $e); + $this->assertInstanceOf(UniqueException::class, $e); } // Create new document @@ -802,7 +803,7 @@ public function testOneToOneTwoWayRelationship(): void ); $this->fail('Failed to throw exception'); } catch (Exception $e) { - $this->assertInstanceOf(DuplicateException::class, $e); + $this->assertInstanceOf(UniqueException::class, $e); } $city1 = $database->getDocument('city', 'city1'); From 726868aa99de72a9bf215cc8b76a6285ccba6762 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 12 Oct 2025 09:41:35 +0300 Subject: [PATCH 02/13] SQLite.php Exception --- src/Database/Adapter/SQLite.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index a892b6626..8f3c7e6eb 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -9,7 +9,7 @@ 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\Unique as UniqueException; use Utopia\Database\Exception\NotFound as NotFoundException; use Utopia\Database\Exception\Timeout as TimeoutException; use Utopia\Database\Exception\Transaction as TransactionException; @@ -1250,7 +1250,7 @@ protected function processException(PDOException $e): \Exception // Duplicate if ($e->getCode() === 'HY000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1) { - return new DuplicateException('Document already exists', $e->getCode(), $e); + return new UniqueException('Document already exists', $e->getCode(), $e); } return $e; From bbd9f4f1443370406e18a592d5f58c9f02dcb506 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 12 Oct 2025 09:41:57 +0300 Subject: [PATCH 03/13] formatting --- src/Database/Adapter/SQLite.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index 8f3c7e6eb..8e79079a9 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -9,10 +9,10 @@ use Utopia\Database\Document; use Utopia\Database\Exception as DatabaseException; use Utopia\Database\Exception\Duplicate; -use Utopia\Database\Exception\Unique as UniqueException; 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; /** From c9daebf9169c87ccabae1b1a7ce22044ce672803 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 12 Oct 2025 10:04:44 +0300 Subject: [PATCH 04/13] SQLite tests --- src/Database/Adapter/SQLite.php | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index 8e79079a9..20045bfd4 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -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); } @@ -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; @@ -1243,13 +1236,16 @@ public function getKeywords(): array protected function processException(PDOException $e): \Exception { + var_dump('processException'); + var_dump($e->getCode()); + var_dump($e->errorInfo[1]); // Timeout if ($e->getCode() === 'HY000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 3024) { return new TimeoutException('Query timed out', $e->getCode(), $e); } - // Duplicate - if ($e->getCode() === 'HY000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1) { + // Duplicate row + if ($e->getCode() === '23000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 19) { return new UniqueException('Document already exists', $e->getCode(), $e); } From e4412d7a332e5ea569fc93460d7f5bb38d0992bf Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 12 Oct 2025 10:10:26 +0300 Subject: [PATCH 05/13] SQLite tests --- src/Database/Adapter/MariaDB.php | 4 ++-- src/Database/Adapter/SQLite.php | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 8ed9adaf3..afb9d41af 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -817,7 +817,7 @@ public function deleteIndex(string $collection, string $id): bool * @return Document * @throws Exception * @throws PDOException - * @throws DuplicateException + * @throws UniqueException * @throws \Throwable */ public function createDocument(Document $collection, Document $document): Document @@ -943,7 +943,7 @@ public function createDocument(Document $collection, Document $document): Docume * @return Document * @throws Exception * @throws PDOException - * @throws DuplicateException + * @throws UniqueException * @throws \Throwable */ public function updateDocument(Document $collection, string $id, Document $document, bool $skipPermissions): Document diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index 20045bfd4..cd9d72117 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -8,7 +8,6 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception as DatabaseException; -use Utopia\Database\Exception\Duplicate; use Utopia\Database\Exception\NotFound as NotFoundException; use Utopia\Database\Exception\Timeout as TimeoutException; use Utopia\Database\Exception\Transaction as TransactionException; @@ -517,7 +516,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 { @@ -636,7 +635,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 { @@ -1236,9 +1235,6 @@ public function getKeywords(): array protected function processException(PDOException $e): \Exception { - var_dump('processException'); - var_dump($e->getCode()); - var_dump($e->errorInfo[1]); // Timeout if ($e->getCode() === 'HY000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 3024) { return new TimeoutException('Query timed out', $e->getCode(), $e); From 53e1a2e05951a39a450997203f9f8b6455ae5d7b Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 15 Oct 2025 11:12:06 +0300 Subject: [PATCH 06/13] Mysql --- src/Database/Adapter/MariaDB.php | 10 ++- src/Database/Exception/Unique.php | 4 +- tests/e2e/Adapter/Scopes/DocumentTests.php | 85 ++++++++++++------- .../Scopes/Relationships/OneToOneTests.php | 5 +- 4 files changed, 66 insertions(+), 38 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index afb9d41af..063f83b09 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1796,7 +1796,15 @@ protected function processException(PDOException $e): \Exception // Duplicate row if ($e->getCode() === '23000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1062) { - return new UniqueException('Document already exists', $e->getCode(), $e); + var_dump($e->getMessage()); + if (preg_match("/for key '(?:[^.]+\.)?([^']+)'/", $e->getMessage(), $m)) { + if($m[1] === '_uid' || $m[1] === 'PRIMARY') { + var_dump($m); + return new UniqueException('Document already exists', $e->getCode(), $e); + } + } + + return new DuplicateException('Document already exists', $e->getCode(), $e); } // Data is too big for column resize diff --git a/src/Database/Exception/Unique.php b/src/Database/Exception/Unique.php index 57a4dc961..d8e2fe501 100644 --- a/src/Database/Exception/Unique.php +++ b/src/Database/Exception/Unique.php @@ -2,8 +2,6 @@ namespace Utopia\Database\Exception; -use Utopia\Database\Exception; - -class Unique extends Exception +class Unique extends Duplicate { } diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index d5b221ac1..6ed462954 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -4730,36 +4730,41 @@ public function testWritePermissionsUpdateFailure(Document $document): Document */ public function testUniqueIndexDuplicate(): void { - $this->expectException(UniqueException::class); - /** @var Database $database */ $database = static::getDatabase(); $this->assertEquals(true, $database->createIndex('movies', 'uniqueIndex', Database::INDEX_UNIQUE, ['name'], [128], [Database::ORDER_ASC])); - $database->createDocument('movies', new Document([ - '$permissions' => [ - Permission::read(Role::any()), - Permission::read(Role::user('1')), - Permission::read(Role::user('2')), - Permission::create(Role::any()), - Permission::create(Role::user('1x')), - Permission::create(Role::user('2x')), - Permission::update(Role::any()), - Permission::update(Role::user('1x')), - Permission::update(Role::user('2x')), - Permission::delete(Role::any()), - Permission::delete(Role::user('1x')), - Permission::delete(Role::user('2x')), - ], - 'name' => 'Frozen', - 'director' => 'Chris Buck & Jennifer Lee', - 'year' => 2013, - 'price' => 39.50, - 'active' => true, - 'genres' => ['animation', 'kids'], - 'with-dash' => 'Works4' - ])); + try { + $database->createDocument('movies', new Document([ + '$permissions' => [ + Permission::read(Role::any()), + Permission::read(Role::user('1')), + Permission::read(Role::user('2')), + Permission::create(Role::any()), + Permission::create(Role::user('1x')), + Permission::create(Role::user('2x')), + Permission::update(Role::any()), + Permission::update(Role::user('1x')), + Permission::update(Role::user('2x')), + Permission::delete(Role::any()), + Permission::delete(Role::user('1x')), + Permission::delete(Role::user('2x')), + ], + 'name' => 'Frozen', + 'director' => 'Chris Buck & Jennifer Lee', + 'year' => 2013, + 'price' => 39.50, + 'active' => true, + 'genres' => ['animation', 'kids'], + 'with-dash' => 'Works4' + ])); + + $this->fail('Failed to throw exception'); + } catch (Throwable $e) { + $this->assertInstanceOf(DuplicateException::class, $e); + $this->assertNotInstanceOf(UniqueException::class, $e); + } } /** * @depends testUniqueIndexDuplicate @@ -4795,9 +4800,14 @@ public function testUniqueIndexDuplicateUpdate(): void 'with-dash' => 'Works4' ])); - $this->expectException(UniqueException::class); + try { + $database->updateDocument('movies', $document->getId(), $document->setAttribute('name', 'Frozen')); - $database->updateDocument('movies', $document->getId(), $document->setAttribute('name', 'Frozen')); + $this->fail('Failed to throw exception'); + } catch (Throwable $e) { + $this->assertInstanceOf(DuplicateException::class, $e); + $this->assertNotInstanceOf(UniqueException::class, $e); + } } public function propagateBulkDocuments(string $collection, int $amount = 10, bool $documentSecurity = false): void @@ -5300,8 +5310,15 @@ public function testExceptionDuplicate(Document $document): void $document->setAttribute('$id', 'duplicated'); $database->createDocument($document->getCollection(), $document); - $this->expectException(UniqueException::class); - $database->createDocument($document->getCollection(), $document); + + try { + $database->createDocument($document->getCollection(), $document); + + $this->fail('Failed to throw exception'); + } catch (Throwable $e) { + $this->assertInstanceOf(DuplicateException::class, $e); + $this->assertInstanceOf(UniqueException::class, $e); + } } /** @@ -5318,8 +5335,14 @@ public function testExceptionCaseInsensitiveDuplicate(Document $document): Docum $document->setAttribute('$id', 'CaseSensitive'); - $this->expectException(UniqueException::class); - $database->createDocument($document->getCollection(), $document); + try { + $database->createDocument($document->getCollection(), $document); + + $this->fail('Failed to throw exception'); + } catch (Throwable $e) { + $this->assertInstanceOf(DuplicateException::class, $e); + $this->assertInstanceOf(UniqueException::class, $e); + } return $document; } diff --git a/tests/e2e/Adapter/Scopes/Relationships/OneToOneTests.php b/tests/e2e/Adapter/Scopes/Relationships/OneToOneTests.php index 723647998..52881707b 100644 --- a/tests/e2e/Adapter/Scopes/Relationships/OneToOneTests.php +++ b/tests/e2e/Adapter/Scopes/Relationships/OneToOneTests.php @@ -10,7 +10,6 @@ use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\Restricted as RestrictedException; use Utopia\Database\Exception\Structure as StructureException; -use Utopia\Database\Exception\Unique as UniqueException; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; @@ -293,7 +292,7 @@ public function testOneToOneOneWayRelationship(): void ); $this->fail('Failed to throw duplicate exception'); } catch (Exception $e) { - $this->assertInstanceOf(UniqueException::class, $e); + $this->assertInstanceOf(DuplicateException::class, $e); } // Create new document @@ -803,7 +802,7 @@ public function testOneToOneTwoWayRelationship(): void ); $this->fail('Failed to throw exception'); } catch (Exception $e) { - $this->assertInstanceOf(UniqueException::class, $e); + $this->assertInstanceOf(DuplicateException::class, $e); } $city1 = $database->getDocument('city', 'city1'); From 07f985040965fff7cd6ba613a6289425fab9a353 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 15 Oct 2025 12:48:32 +0300 Subject: [PATCH 07/13] Mysql --- src/Database/Adapter/MariaDB.php | 10 +++++----- src/Database/Adapter/Postgres.php | 10 +++++++++- tests/e2e/Adapter/Scopes/DocumentTests.php | 12 ++++++------ 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 063f83b09..41b2ad98c 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -818,6 +818,7 @@ public function deleteIndex(string $collection, string $id): bool * @throws Exception * @throws PDOException * @throws UniqueException + * @throws DuplicateException * @throws \Throwable */ public function createDocument(Document $collection, Document $document): Document @@ -944,6 +945,7 @@ public function createDocument(Document $collection, Document $document): Docume * @throws Exception * @throws PDOException * @throws UniqueException + * @throws DuplicateException * @throws \Throwable */ public function updateDocument(Document $collection, string $id, Document $document, bool $skipPermissions): Document @@ -1796,15 +1798,13 @@ protected function processException(PDOException $e): \Exception // Duplicate row if ($e->getCode() === '23000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1062) { - var_dump($e->getMessage()); if (preg_match("/for key '(?:[^.]+\.)?([^']+)'/", $e->getMessage(), $m)) { - if($m[1] === '_uid' || $m[1] === 'PRIMARY') { - var_dump($m); - return new UniqueException('Document already exists', $e->getCode(), $e); + if ($m[1] === '_uid') { + return new DuplicateException('Document already exists', $e->getCode(), $e); } } - 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 b36e207ce..ec6eb7ca3 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1925,7 +1925,15 @@ protected function processException(PDOException $e): \Exception // Duplicate row if ($e->getCode() === '23505' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 7) { - return new UniqueException('Document already exists', $e->getCode(), $e); + var_dump($e->getMessage()); + if (preg_match("/for key '(?:[^.]+\.)?([^']+)'/", $e->getMessage(), $m)) { + if ($m[1] === '_uid' || $m[1] === 'PRIMARY') { + var_dump($m); + return new UniqueException('Document already exists', $e->getCode(), $e); + } + } + + return new DuplicateException('Document already exists', $e->getCode(), $e); } // Data is too big for column resize diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index 6ed462954..7d5f60802 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -4762,8 +4762,8 @@ public function testUniqueIndexDuplicate(): void $this->fail('Failed to throw exception'); } catch (Throwable $e) { + $this->assertInstanceOf(UniqueException::class, $e); $this->assertInstanceOf(DuplicateException::class, $e); - $this->assertNotInstanceOf(UniqueException::class, $e); } } /** @@ -4805,8 +4805,8 @@ public function testUniqueIndexDuplicateUpdate(): void $this->fail('Failed to throw exception'); } catch (Throwable $e) { + $this->assertInstanceOf(UniqueException::class, $e); $this->assertInstanceOf(DuplicateException::class, $e); - $this->assertNotInstanceOf(UniqueException::class, $e); } } @@ -5330,18 +5330,18 @@ public function testExceptionCaseInsensitiveDuplicate(Document $document): Docum $database = static::getDatabase(); $document->setAttribute('$id', 'caseSensitive'); - $document->setAttribute('$sequence', '200'); - $database->createDocument($document->getCollection(), $document); + $document->removeAttribute('$sequence'); + $database->createDocument($document->getCollection(), $document); $document->setAttribute('$id', 'CaseSensitive'); + $document->removeAttribute('$sequence'); try { $database->createDocument($document->getCollection(), $document); - $this->fail('Failed to throw exception'); } catch (Throwable $e) { $this->assertInstanceOf(DuplicateException::class, $e); - $this->assertInstanceOf(UniqueException::class, $e); + $this->assertNotInstanceOf(UniqueException::class, $e); } return $document; From 231d53e169a7ecc463fa7d41fce4d5a7acc911e2 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 15 Oct 2025 15:37:43 +0300 Subject: [PATCH 08/13] SQLite.php --- src/Database/Adapter/MariaDB.php | 4 ++-- src/Database/Adapter/Postgres.php | 13 +++++++------ src/Database/Adapter/SQLite.php | 7 +++++++ tests/e2e/Adapter/Scopes/DocumentTests.php | 9 ++++++--- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 41b2ad98c..2d264aa55 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1798,8 +1798,8 @@ protected function processException(PDOException $e): \Exception // Duplicate row if ($e->getCode() === '23000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1062) { - if (preg_match("/for key '(?:[^.]+\.)?([^']+)'/", $e->getMessage(), $m)) { - if ($m[1] === '_uid') { + if (preg_match("/for key '(?:[^.]+\.)?([^']+)'/", $e->getMessage(), $matches)) { + if ($matches[1] === '_uid') { return new DuplicateException('Document already exists', $e->getCode(), $e); } } diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index ec6eb7ca3..06ae2a1ea 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1925,15 +1925,16 @@ protected function processException(PDOException $e): \Exception // Duplicate row if ($e->getCode() === '23505' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 7) { - var_dump($e->getMessage()); - if (preg_match("/for key '(?:[^.]+\.)?([^']+)'/", $e->getMessage(), $m)) { - if ($m[1] === '_uid' || $m[1] === 'PRIMARY') { - var_dump($m); - return new UniqueException('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 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 cd9d72117..09bafde43 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -8,6 +8,7 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception as DatabaseException; +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; @@ -1242,6 +1243,12 @@ protected function processException(PDOException $e): \Exception // Duplicate row if ($e->getCode() === '23000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 19) { + if (preg_match('/UNIQUE constraint failed:\s+([^.]+)\.([^\s]+)/', $e->getMessage(), $m)) { + if ($m[2] === '_uid') { + return new DuplicateException('Document already exists', $e->getCode(), $e); + } + } + return new UniqueException('Document already exists', $e->getCode(), $e); } diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index 7d5f60802..3b21a3654 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -5309,15 +5309,17 @@ public function testExceptionDuplicate(Document $document): void $database = static::getDatabase(); $document->setAttribute('$id', 'duplicated'); + $document->removeAttribute('$sequence'); + $database->createDocument($document->getCollection(), $document); + $document->removeAttribute('$sequence'); try { $database->createDocument($document->getCollection(), $document); - $this->fail('Failed to throw exception'); } catch (Throwable $e) { $this->assertInstanceOf(DuplicateException::class, $e); - $this->assertInstanceOf(UniqueException::class, $e); + $this->assertNotInstanceOf(UniqueException::class, $e); } } @@ -5333,13 +5335,14 @@ public function testExceptionCaseInsensitiveDuplicate(Document $document): Docum $document->removeAttribute('$sequence'); $database->createDocument($document->getCollection(), $document); - $document->setAttribute('$id', 'CaseSensitive'); + $document->setAttribute('$id', 'caseSensitive'); // Revert this $document->removeAttribute('$sequence'); try { $database->createDocument($document->getCollection(), $document); $this->fail('Failed to throw exception'); } catch (Throwable $e) { + var_dump($e); $this->assertInstanceOf(DuplicateException::class, $e); $this->assertNotInstanceOf(UniqueException::class, $e); } From 2b59a27f467c61c7b05ac211d304956dc69923f7 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 15 Oct 2025 15:51:00 +0300 Subject: [PATCH 09/13] SQLite.php --- src/Database/Adapter/Postgres.php | 8 +++++--- src/Database/Adapter/SQLite.php | 14 +++++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 06ae2a1ea..a3ed2711b 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1924,11 +1924,13 @@ protected function processException(PDOException $e): \Exception } // Duplicate row - if ($e->getCode() === '23505' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 7) { - if (preg_match('/Key \(([^)]+)\)=\(.+\) already exists/', $e->getMessage(), $matches)) { - $columns = array_map('trim', explode(',', $matches[1])); + if ($e->getCode() === '23000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 19) { + if (preg_match_all('/\b([^.]+)\.([^\s,]+)/', $e->getMessage(), $matches)) { + $columns = $matches[2]; sort($columns); + $target = $this->sharedTables ? ['_tenant', '_uid'] : ['_uid']; + if ($columns == $target) { return new DuplicateException('Document already exists', $e->getCode(), $e); } diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index 09bafde43..97fbec6c3 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -1242,9 +1242,17 @@ protected function processException(PDOException $e): \Exception } // Duplicate row - if ($e->getCode() === '23000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 19) { - if (preg_match('/UNIQUE constraint failed:\s+([^.]+)\.([^\s]+)/', $e->getMessage(), $m)) { - if ($m[2] === '_uid') { + 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); + + var_dump($columns); + + if ($columns === ['_tenant', '_uid'] || in_array('_uid', $columns)) { return new DuplicateException('Document already exists', $e->getCode(), $e); } } From 22648b0b46855c3fffa7e5bbdbbc8c0131356924 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 15 Oct 2025 15:55:23 +0300 Subject: [PATCH 10/13] Remove var_dump --- tests/e2e/Adapter/Scopes/DocumentTests.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/e2e/Adapter/Scopes/DocumentTests.php b/tests/e2e/Adapter/Scopes/DocumentTests.php index 3b21a3654..4a20a52cd 100644 --- a/tests/e2e/Adapter/Scopes/DocumentTests.php +++ b/tests/e2e/Adapter/Scopes/DocumentTests.php @@ -5335,14 +5335,13 @@ public function testExceptionCaseInsensitiveDuplicate(Document $document): Docum $document->removeAttribute('$sequence'); $database->createDocument($document->getCollection(), $document); - $document->setAttribute('$id', 'caseSensitive'); // Revert this + $document->setAttribute('$id', 'CaseSensitive'); $document->removeAttribute('$sequence'); try { $database->createDocument($document->getCollection(), $document); $this->fail('Failed to throw exception'); } catch (Throwable $e) { - var_dump($e); $this->assertInstanceOf(DuplicateException::class, $e); $this->assertNotInstanceOf(UniqueException::class, $e); } From 6c651e7158a4933b0168fc2f17b4e999fdba59d5 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 15 Oct 2025 16:06:48 +0300 Subject: [PATCH 11/13] Postgres.php --- src/Database/Adapter/Postgres.php | 8 +++----- src/Database/Adapter/SQLite.php | 2 -- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index a3ed2711b..06ae2a1ea 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1924,13 +1924,11 @@ protected function processException(PDOException $e): \Exception } // Duplicate row - if ($e->getCode() === '23000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 19) { - if (preg_match_all('/\b([^.]+)\.([^\s,]+)/', $e->getMessage(), $matches)) { - $columns = $matches[2]; + if ($e->getCode() === '23505' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 7) { + 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); } diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index 97fbec6c3..c465acec0 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -1250,8 +1250,6 @@ protected function processException(PDOException $e): \Exception $columns = array_map(fn ($m) => $m[2], $matches); sort($columns); - var_dump($columns); - if ($columns === ['_tenant', '_uid'] || in_array('_uid', $columns)) { return new DuplicateException('Document already exists', $e->getCode(), $e); } From ce620a6ae962debee4d043a2796592335abe267f Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 16 Oct 2025 08:26:03 +0300 Subject: [PATCH 12/13] Mysql Use str_ends_with --- src/Database/Adapter/MariaDB.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 2d264aa55..5e3e8084d 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1798,10 +1798,8 @@ protected function processException(PDOException $e): \Exception // Duplicate row if ($e->getCode() === '23000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1062) { - if (preg_match("/for key '(?:[^.]+\.)?([^']+)'/", $e->getMessage(), $matches)) { - if ($matches[1] === '_uid') { - return new DuplicateException('Document already exists', $e->getCode(), $e); - } + if (str_ends_with($e->getMessage(), "._uid'")) { + return new DuplicateException('Document already exists', $e->getCode(), $e); } return new UniqueException('Document already exists', $e->getCode(), $e); From 6a9c42ab809189cb3023cfa23c992e6a870aaf61 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 16 Oct 2025 12:06:03 +0300 Subject: [PATCH 13/13] Fix mariaDB str_ends_with --- src/Database/Adapter/MariaDB.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 5e3e8084d..e2a1c234a 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1798,7 +1798,7 @@ protected function processException(PDOException $e): \Exception // Duplicate row if ($e->getCode() === '23000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1062) { - if (str_ends_with($e->getMessage(), "._uid'")) { + if (str_ends_with($e->getMessage(), "._uid'") || str_ends_with($e->getMessage(), "'_uid'")) { return new DuplicateException('Document already exists', $e->getCode(), $e); }