From 5740cecbe5cc87325acfad4fad59be59e8274b7f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 2 Apr 2026 02:08:19 +1300 Subject: [PATCH] (fix): pass collection to getDatabasesDB for zero-query ID mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Endpoints pass the collection document they already have to getDatabasesDB. The factory registers the single collection mapping on the Metadata decorator — no bulk find queries, no static cache, no dbForProject dependency. The decorator is now purely stateless with zero database overhead. Collection ID resolution uses only the pre-registered mapping from the endpoint's request parameters. Co-Authored-By: Claude Opus 4.6 (1M context) --- app/init/resources.php | 39 +++++-------------- .../Http/Databases/Collections/Create.php | 2 +- .../Http/Databases/Collections/Delete.php | 2 +- .../Documents/Attribute/Decrement.php | 2 +- .../Documents/Attribute/Increment.php | 2 +- .../Collections/Documents/Bulk/Delete.php | 2 +- .../Collections/Documents/Bulk/Update.php | 2 +- .../Collections/Documents/Bulk/Upsert.php | 2 +- .../Collections/Documents/Create.php | 2 +- .../Collections/Documents/Delete.php | 2 +- .../Databases/Collections/Documents/Get.php | 2 +- .../Collections/Documents/Logs/XList.php | 2 +- .../Collections/Documents/Update.php | 2 +- .../Collections/Documents/Upsert.php | 2 +- .../Databases/Collections/Documents/XList.php | 2 +- .../Databases/Collections/Indexes/Create.php | 2 +- .../Http/Databases/Collections/Update.php | 2 +- .../Http/Databases/Collections/Usage/Get.php | 2 +- .../Http/VectorsDB/Collections/Create.php | 2 +- .../Http/VectorsDB/Collections/Update.php | 2 +- .../Utopia/Database/Hooks/Metadata.php | 23 +---------- 21 files changed, 31 insertions(+), 69 deletions(-) diff --git a/app/init/resources.php b/app/init/resources.php index f07dcfcce4..75fe4cfd79 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -707,9 +707,9 @@ Http::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authori return $database; }, ['pools', 'cache', 'authorization']); -Http::setResource('getDatabasesDB', function (Group $pools, Database $dbForProject, Cache $cache, Document $project, Request $request, UsageContext $usage, Authorization $authorization) { +Http::setResource('getDatabasesDB', function (Group $pools, Cache $cache, Document $project, Request $request, UsageContext $usage, Authorization $authorization) { - return function (Document $database) use ($pools, $dbForProject, $cache, $project, $request, $usage, $authorization): Database { + return function (Document $database, ?Document $collection = null) use ($pools, $cache, $project, $request, $usage, $authorization): Database { $originalDatabase = $database; $context = str_contains($request->getURI(), '/tablesdb/') ? 'table' : 'collection'; $databaseDSN = $database->getAttribute('database', $project->getAttribute('database', '')); @@ -774,32 +774,13 @@ Http::setResource('getDatabasesDB', function (Group $pools, Database $dbForProje context: $context, ); - // Pre-populate collection ID mapping from static cache or single query - $databaseSequence = $originalDatabase->getSequence(); - $cachedMap = Metadata::getCachedMap($databaseSequence); - if ($cachedMap !== null) { - foreach ($cachedMap as $k => $v) { - $metadata->setCollectionId($k, $v); - } - } else { - try { - $dbPrefix = 'database_' . $databaseSequence; - $collections = $authorization->skip( - fn () => $dbForProject->silent( - fn () => $dbForProject->find($dbPrefix, [ - \Utopia\Database\Query::limit(5000), - ]) - ) - ); - foreach ($collections as $col) { - $seq = $col->getSequence(); - if ($seq !== null) { - $metadata->setCollectionId('collection_' . $seq, $col->getId()); - $metadata->setCollectionId($dbPrefix . '_collection_' . $seq, $col->getId()); - } - } - } catch (\Throwable) { - // Database may not have collections yet + // Register collection ID mapping if collection document is provided + if ($collection !== null && !$collection->isEmpty()) { + $seq = $collection->getSequence(); + if ($seq !== null) { + $dbPrefix = 'database_' . $originalDatabase->getSequence(); + $metadata->setCollectionId('collection_' . $seq, $collection->getId()); + $metadata->setCollectionId($dbPrefix . '_collection_' . $seq, $collection->getId()); } } @@ -821,7 +802,7 @@ Http::setResource('getDatabasesDB', function (Group $pools, Database $dbForProje return $database; }; -}, ['pools','dbForProject','cache','project','request','usage','authorization']); +}, ['pools','cache','project','request','usage','authorization']); Http::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) { $databases = []; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Create.php index 0b8dbd38df..f6bf9e752a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Create.php @@ -129,7 +129,7 @@ class Create extends Action /** * @var Database $dbForDatabases */ - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); $collectionKey = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); $databaseKey = 'database_' . $database->getSequence(); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Delete.php index 7a5b73f7db..dc56cf65ef 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Delete.php @@ -86,7 +86,7 @@ class Delete extends Action throw new Exception(Exception::GENERAL_SERVER_ERROR, "Failed to remove $type from DB"); } - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); $dbForDatabases->purgeCachedCollection('database_' . $database->getSequence() . '_collection_' . $collection->getSequence()); $queueForDatabase diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Decrement.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Decrement.php index b9796cd52b..d494607368 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Decrement.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Decrement.php @@ -173,7 +173,7 @@ class Decrement extends Action return; } - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); try { $document = $dbForDatabases->decreaseDocumentAttribute( collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Increment.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Increment.php index 7e42fcfd41..a47c665644 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Increment.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Attribute/Increment.php @@ -173,7 +173,7 @@ class Increment extends Action return; } - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); try { $document = $dbForDatabases->increaseDocumentAttribute( collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php index 2ab5eab877..1e6a4a9e91 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php @@ -165,7 +165,7 @@ class Delete extends Action return; } - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); $documents = []; try { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php index 11bc43eba9..387c3c1161 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php @@ -191,7 +191,7 @@ class Update extends Action return; } - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); $documents = []; try { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index e28c6b9583..2f487488eb 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -167,7 +167,7 @@ class Upsert extends Action return; } - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); $upserted = []; try { 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 478834f656..c0ddcfc40b 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 @@ -358,7 +358,7 @@ class Create extends Action return; } - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); try { $created = []; $dbForDatabases->withPreserveDates( diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index 844e406b0b..4719748b20 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -121,7 +121,7 @@ class Delete extends Action throw new Exception($this->getParentNotFoundException(), params: [$collectionId]); } - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); // Read permission should not be required for delete $collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php index 1f4cca9ded..6a219106b4 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Get.php @@ -88,7 +88,7 @@ class Get extends Action $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId)); - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception($this->getParentNotFoundException(), params: [$collectionId]); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php index c7d5e35b67..07f4ea8ff6 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php @@ -90,7 +90,7 @@ class XList extends Action throw new Exception($this->getParentNotFoundException(), params: [$collectionId]); } - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); $document = $dbForDatabases->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId); if ($document->isEmpty()) { throw new Exception($this->getNotFoundException(), params: [$documentId]); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php index fa35089022..07ede3fb67 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php @@ -122,7 +122,7 @@ class Update extends Action $data = $this->parseOperators($data, $collection); } - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); // Read permission should not be required for update /** @var Document $document */ $collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index c95206c58b..684ffec099 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -127,7 +127,7 @@ class Upsert extends Action $data = $this->parseOperators($data, $collection); } - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); $allowedPermissions = [ PermissionType::Read, PermissionType::Update, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php index c30b175e02..0017fbc2d9 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php @@ -104,7 +104,7 @@ class XList extends Action throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); } - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); $cursor = Query::getCursorQueries($queries, false); $cursor = \reset($cursor); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php index f135f45bff..2dc606a218 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Create.php @@ -109,7 +109,7 @@ class Create extends Action Query::equal('databaseInternalId', [$db->getSequence()]) ], 61); - $dbForDatabases = $getDatabasesDB($db); + $dbForDatabases = $getDatabasesDB($db, $collection); $limit = $dbForDatabases->getLimitForIndexes(); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Update.php index 5d9d425d71..217002f346 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Update.php @@ -111,7 +111,7 @@ class Update extends Action ->setAttribute('search', \implode(' ', [$collectionId, $searchName])) ); - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); $dbForDatabases->updateCollection('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $permissions, $documentSecurity); $queueForEvents diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Usage/Get.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Usage/Get.php index 37213f1061..65f3d325ab 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Usage/Get.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Usage/Get.php @@ -77,7 +77,7 @@ class Get extends Action { $database = $dbForProject->getDocument('databases', $databaseId); $collectionDocument = $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId); - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collectionDocument); $collection = $dbForDatabases->getCollection('database_' . $database->getSequence() . '_collection_' . $collectionDocument->getSequence()); if ($collection->isEmpty()) { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Create.php index 9bff792c29..155f8775d9 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Create.php @@ -117,7 +117,7 @@ class Create extends CollectionAction throw new Exception(Exception::DATABASE_NOT_FOUND); } /** @var Database $dbForDatabases */ - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); $collections = (Config::getParam('collections', [])['vectorsdb'] ?? [])['collections'] ?? []; $attributes = \array_map(function (Attribute $attribute) use ($dimension) { diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Update.php index f8ba767e7e..2637f2292a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Update.php @@ -104,7 +104,7 @@ class Update extends CollectionAction ->setAttribute('search', \implode(' ', [$collectionId, $name ?? $collection->getAttribute('name')])) ); - $dbForDatabases = $getDatabasesDB($database); + $dbForDatabases = $getDatabasesDB($database, $collection); $dbForDatabases->updateCollection('database_' . $database->getSequence() . '_collection_' . $updated->getSequence(), $permissions, $documentSecurity); $queueForEvents diff --git a/src/Appwrite/Utopia/Database/Hooks/Metadata.php b/src/Appwrite/Utopia/Database/Hooks/Metadata.php index c8f869c8db..3a18d0956b 100644 --- a/src/Appwrite/Utopia/Database/Hooks/Metadata.php +++ b/src/Appwrite/Utopia/Database/Hooks/Metadata.php @@ -12,8 +12,8 @@ use Utopia\Query\Schema\ColumnType; * Stamps database/collection metadata onto every document returned from the database, * and recursively decorates nested relationship documents. * - * Collection ID mapping is pre-populated by getDatabasesDB via setCollectionId() - * and cached statically per database sequence for the Swoole worker lifetime. + * Collection ID mappings are registered by endpoint actions via setCollectionId() + * before querying documents. No bulk queries or static caches are needed. */ class Metadata implements Decorator { @@ -23,9 +23,6 @@ class Metadata implements Decorator /** @var array internal collection name -> user-facing collection ID */ private array $collectionIdMap = []; - /** @var array> static cache keyed by database sequence */ - private static array $staticMaps = []; - private int $operations = 0; public function __construct( @@ -36,26 +33,10 @@ class Metadata implements Decorator /** * Register a mapping from internal collection name to user-facing collection ID. - * Also updates the static cache for subsequent requests. */ public function setCollectionId(string $internalName, string $externalId): void { $this->collectionIdMap[$internalName] = $externalId; - $seq = $this->database->getSequence(); - self::$staticMaps[$seq][$internalName] = $externalId; - } - - /** - * Get the cached map for a database sequence, or null if not cached. - * - * @return array|null - */ - public static function getCachedMap(?string $sequence): ?array - { - if ($sequence === null) { - return null; - } - return self::$staticMaps[$sequence] ?? null; } public function decorate(Event $event, Document $collection, Document $document): Document