From 4d85ab0dcd759d98a1c46b08c3e8c40e91ff1f64 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 2 Apr 2026 03:40:01 +1300 Subject: [PATCH] (fix): fix related collection lookup by sequence, increase schema timeouts - Related collection lookup used internal name as document ID, but Appwrite collections have user-facing IDs. Extract sequence from internal name and use findOne by $sequence instead. - Increase all schema polling timeouts from 240s to 360s for CI dedicated mode parallel load. Co-Authored-By: Claude Opus 4.6 (1M context) --- app/init/resources.php | 34 +++++++++++++++++++----------- tests/e2e/Scopes/SchemaPolling.php | 12 +++++------ 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/app/init/resources.php b/app/init/resources.php index 1f17a760ba..ca5d26a3e5 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -791,23 +791,33 @@ Http::setResource('getDatabasesDB', function (Group $pools, Database $dbForProje continue; } $options = $attr->getAttribute('options', []); - $relatedId = \is_array($options) ? ($options['relatedCollection'] ?? null) : null; - $relatedId ??= $attr->getAttribute('relatedCollection'); - if ($relatedId === null) { + $relatedInternalName = \is_array($options) ? ($options['relatedCollection'] ?? null) : null; + $relatedInternalName ??= $attr->getAttribute('relatedCollection'); + if ($relatedInternalName === null) { continue; } - // Look up the related collection from dbForProject - $relatedCol = $authorization->skip( - fn () => $dbForProject->silent( - fn () => $dbForProject->getDocument($dbPrefix, $relatedId) - ) - ); - if (!$relatedCol->isEmpty()) { - $relSeq = $relatedCol->getSequence(); - if ($relSeq !== null) { + // Extract sequence from internal name (e.g., 'collection_16' → '16') + $parts = \explode('_', $relatedInternalName); + $relSeq = \end($parts); + if (!\is_numeric($relSeq) && !\str_contains($relSeq, '-')) { + continue; + } + // Find the Appwrite collection document by sequence + try { + $relatedCol = $authorization->skip( + fn () => $dbForProject->silent( + fn () => $dbForProject->findOne($dbPrefix, [ + \Utopia\Database\Query::equal('$sequence', [$relSeq]), + \Utopia\Database\Query::select(['$id', '$sequence']), + ]) + ) + ); + if ($relatedCol !== null && !$relatedCol->isEmpty()) { $metadata->setCollectionId('collection_' . $relSeq, $relatedCol->getId()); $metadata->setCollectionId($dbPrefix . '_collection_' . $relSeq, $relatedCol->getId()); } + } catch (\Throwable) { + // Skip — related collection may not exist yet } } } diff --git a/tests/e2e/Scopes/SchemaPolling.php b/tests/e2e/Scopes/SchemaPolling.php index 867ff3213b..b1410785ae 100644 --- a/tests/e2e/Scopes/SchemaPolling.php +++ b/tests/e2e/Scopes/SchemaPolling.php @@ -20,7 +20,7 @@ trait SchemaPolling * @param int $timeoutMs Maximum time to wait in milliseconds * @param int $waitMs Time between polling attempts in milliseconds */ - protected function waitForAttribute(string $databaseId, string $containerId, string $attributeKey, int $timeoutMs = 240000, int $waitMs = 500): void + protected function waitForAttribute(string $databaseId, string $containerId, string $attributeKey, int $timeoutMs = 360000, int $waitMs = 500): void { if (!$this->getSupportForAttributes()) { return; @@ -56,7 +56,7 @@ trait SchemaPolling * @param int $timeoutMs Maximum time to wait in milliseconds * @param int $waitMs Time between polling attempts in milliseconds */ - protected function waitForAttributes(string $databaseId, string $containerId, array $attributeKeys, int $timeoutMs = 240000, int $waitMs = 500): void + protected function waitForAttributes(string $databaseId, string $containerId, array $attributeKeys, int $timeoutMs = 360000, int $waitMs = 500): void { $this->assertEventually(function () use ($databaseId, $containerId, $attributeKeys) { $container = $this->client->call( @@ -100,7 +100,7 @@ trait SchemaPolling * @param int $timeoutMs Maximum time to wait in milliseconds * @param int $waitMs Time between polling attempts in milliseconds */ - protected function waitForAttributeCount(string $databaseId, string $containerId, int $count, int $timeoutMs = 240000, int $waitMs = 500): void + protected function waitForAttributeCount(string $databaseId, string $containerId, int $count, int $timeoutMs = 360000, int $waitMs = 500): void { $this->assertEventually(function () use ($databaseId, $containerId, $count) { $container = $this->client->call( @@ -142,7 +142,7 @@ trait SchemaPolling * @param int $timeoutMs Maximum time to wait in milliseconds * @param int $waitMs Time between polling attempts in milliseconds */ - protected function waitForIndex(string $databaseId, string $containerId, string $indexKey, int $timeoutMs = 240000, int $waitMs = 500): void + protected function waitForIndex(string $databaseId, string $containerId, string $indexKey, int $timeoutMs = 360000, int $waitMs = 500): void { $this->assertEventually(function () use ($databaseId, $containerId, $indexKey) { $index = $this->client->call( @@ -176,7 +176,7 @@ trait SchemaPolling * @param int $timeoutMs Maximum time to wait in milliseconds * @param int $waitMs Time between polling attempts in milliseconds */ - protected function waitForAllIndexes(string $databaseId, string $containerId, int $timeoutMs = 240000, int $waitMs = 500): void + protected function waitForAllIndexes(string $databaseId, string $containerId, int $timeoutMs = 360000, int $waitMs = 500): void { $this->assertEventually(function () use ($databaseId, $containerId) { $container = $this->client->call( @@ -210,7 +210,7 @@ trait SchemaPolling * @param int $timeoutMs Maximum time to wait in milliseconds * @param int $waitMs Time between polling attempts in milliseconds */ - protected function waitForAllAttributes(string $databaseId, string $containerId, int $timeoutMs = 240000, int $waitMs = 500): void + protected function waitForAllAttributes(string $databaseId, string $containerId, int $timeoutMs = 360000, int $waitMs = 500): void { $this->assertEventually(function () use ($databaseId, $containerId) { $container = $this->client->call(