From 0d95ba99d3ea9430b293e9dde6eff7674d5ae363 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 16 Oct 2025 16:59:02 +0300 Subject: [PATCH] Add DOCUMENT_UNIQUE_VIOLATION --- app/config/errors.php | 10 ++++++ src/Appwrite/Extend/Exception.php | 2 ++ .../Collections/Documents/Action.php | 10 ++++++ .../Collections/Documents/Create.php | 4 +++ .../Databases/Legacy/DatabasesBase.php | 36 +++++++++++++++++++ 5 files changed, 62 insertions(+) diff --git a/app/config/errors.php b/app/config/errors.php index 2e18f05797..a45f092eb2 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -762,6 +762,11 @@ return [ 'description' => 'Document with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.', 'code' => 409, ], + Exception::DOCUMENT_UNIQUE_VIOLATION => [ + 'name' => Exception::DOCUMENT_UNIQUE_VIOLATION, + 'description' => 'Document unique constraint violation. Please try again with different data.', + 'code' => 409, + ], Exception::DOCUMENT_UPDATE_CONFLICT => [ 'name' => Exception::DOCUMENT_UPDATE_CONFLICT, 'description' => 'Remote document is newer than local.', @@ -799,6 +804,11 @@ return [ 'description' => 'Row with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.', 'code' => 409, ], + Exception::ROW_UNIQUE_VIOLATION => [ + 'name' => Exception::ROW_UNIQUE_VIOLATION, + 'description' => 'Row unique constraint violation. Please try again with different data.', + 'code' => 409, + ], Exception::ROW_UPDATE_CONFLICT => [ 'name' => Exception::ROW_UPDATE_CONFLICT, 'description' => 'Remote row is newer than local.', diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 6f8744568a..b6d3138ea6 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -212,6 +212,7 @@ class Exception extends \Exception public const string DOCUMENT_MISSING_DATA = 'document_missing_data'; public const string DOCUMENT_MISSING_PAYLOAD = 'document_missing_payload'; public const string DOCUMENT_ALREADY_EXISTS = 'document_already_exists'; + public const string DOCUMENT_UNIQUE_VIOLATION = 'document_unique_violation'; public const string DOCUMENT_UPDATE_CONFLICT = 'document_update_conflict'; public const string DOCUMENT_DELETE_RESTRICTED = 'document_delete_restricted'; @@ -221,6 +222,7 @@ class Exception extends \Exception public const string ROW_MISSING_DATA = 'row_missing_data'; public const string ROW_MISSING_PAYLOAD = 'row_missing_payload'; public const string ROW_ALREADY_EXISTS = 'row_already_exists'; + public const string ROW_UNIQUE_VIOLATION = 'row_unique_violation'; public const string ROW_UPDATE_CONFLICT = 'row_update_conflict'; public const string ROW_DELETE_RESTRICTED = 'row_delete_restricted'; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php index 3da89f352c..316287ac15 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php @@ -127,6 +127,16 @@ abstract class Action extends AppwriteAction : Exception::ROW_NOT_FOUND; } + /** + * Get the appropriate already exists exception. + */ + protected function getUniqueException(): string + { + return $this->isCollectionsAPI() + ? Exception::DOCUMENT_UNIQUE_VIOLATION + : Exception::ROW_UNIQUE_VIOLATION; + } + /** * Get the appropriate already exists exception. */ diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php index 521190d3dc..3b1c0a9e4c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php @@ -17,6 +17,7 @@ use Appwrite\Utopia\Response as UtopiaResponse; use Utopia\Database\Database; use Utopia\Database\Document; 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\Relationship as RelationshipException; use Utopia\Database\Exception\Structure as StructureException; @@ -135,6 +136,7 @@ class Create extends Action } public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void { + var_dump('shmuel'); $data = \is_string($data) ? \json_decode($data, true) : $data; @@ -440,6 +442,8 @@ class Create extends Action $documents, ) ); + } catch (UniqueException) { + throw new Exception($this->getUniqueException()); } catch (DuplicateException) { throw new Exception($this->getDuplicateException()); } catch (NotFoundException) { diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index bfc56567ef..3256310f63 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -4289,6 +4289,42 @@ trait DatabasesBase return $data; } + /** + * @depends testDefaultPermissions + */ + public function testUniqueUid(array $data): void + { + /** + * Test duplicate on unique $id + */ + $document = $this->client->call(Client::METHOD_POST, '/databases/' . $data['databaseId'] . '/collections/' . $data['moviesId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'hello', + 'data' => [ + 'title' => 'Hello 1', + 'releaseYear' => 2000 + ] + ]); + + $this->assertEquals(201, $document['headers']['status-code']); + + $document = $this->client->call(Client::METHOD_POST, '/databases/' . $data['databaseId'] . '/collections/' . $data['moviesId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'hello', + 'data' => [ + 'title' => 'Hello 2', + 'releaseYear' => 2000 + ] + ]); + var_dump($document); + $this->assertEquals(409, $document['headers']['status-code']); + $this->assertEquals('shmuel', 'fogel'); + } + /** * @depends testUniqueIndexDuplicate */