diff --git a/app/controllers/general.php b/app/controllers/general.php index 4effed01e1..91e414d077 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -22,11 +22,13 @@ use Appwrite\Utopia\Request\Filters\V16 as RequestV16; use Appwrite\Utopia\Request\Filters\V17 as RequestV17; use Appwrite\Utopia\Request\Filters\V18 as RequestV18; use Appwrite\Utopia\Request\Filters\V19 as RequestV19; +use Appwrite\Utopia\Request\Filters\V20 as RequestV20; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Filters\V16 as ResponseV16; use Appwrite\Utopia\Response\Filters\V17 as ResponseV17; use Appwrite\Utopia\Response\Filters\V18 as ResponseV18; use Appwrite\Utopia\Response\Filters\V19 as ResponseV19; +use Appwrite\Utopia\Response\Filters\V20 as ResponseV20; use Appwrite\Utopia\View; use Executor\Executor; use MaxMind\Db\Reader; @@ -841,8 +843,11 @@ App::init() $request->addFilter(new RequestV18()); } if (version_compare($requestFormat, '1.7.0', '<')) { + $request->addFilter(new RequestV19()); + } + if (version_compare($requestFormat, '1.8.0', '<')) { $dbForProject = $getProjectDB($project); - $request->addFilter(new RequestV19($dbForProject, $route)); + $request->addFilter(new RequestV20($dbForProject, $route)); } } @@ -1013,6 +1018,9 @@ App::init() if (version_compare($responseFormat, '1.7.0', '<')) { $response->addFilter(new ResponseV19()); } + if (version_compare($responseFormat, '1.8.0', '<')) { + $response->addFilter(new ResponseV20()); + } if (version_compare($responseFormat, APP_VERSION_STABLE, '>')) { $response->addHeader('X-Appwrite-Warning', "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is " . APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks"); } diff --git a/src/Appwrite/Utopia/Request/Filters/V19.php b/src/Appwrite/Utopia/Request/Filters/V19.php index 88acda473b..e7789ac0f7 100644 --- a/src/Appwrite/Utopia/Request/Filters/V19.php +++ b/src/Appwrite/Utopia/Request/Filters/V19.php @@ -3,10 +3,6 @@ namespace Appwrite\Utopia\Request\Filters; use Appwrite\Utopia\Request\Filter; -use Utopia\Database\Database; -use Utopia\Database\Exception\Query as QueryException; -use Utopia\Database\Query; -use Utopia\Database\Validator\Authorization; class V19 extends Filter { @@ -14,10 +10,6 @@ class V19 extends Filter public function parse(array $content, string $model): array { switch ($model) { - case 'databases.getDocument': - case 'databases.listDocuments': - $content = $this->manageSelectQueries($content, $model); - break; case 'functions.list': $content = $this->convertQueryAttribute($content, 'deployment', 'deploymentId'); break; @@ -61,104 +53,4 @@ class V19 extends Filter return $content; } - - /** - * From 1.7.x onward, related documents are no longer returned by default to improve performance. - * - * Use `Query::select(['related.*'])` for full documents or `Query::select(['related.key'])` for specific fields. - * - * This filter preserves 1.6.x behavior by including all related documents for backward compatibility with - * `listDocuments` and `getDocument` calls. - */ - protected function manageSelectQueries(array $content, string $model): array - { - $hasWildcard = false; - if (! isset($content['queries'])) { - $hasWildcard = true; - // only query, make it json encoded! - $content['queries'] = [Query::select(['*'])->toString()]; - } - - try { - $parsed = Query::parseQueries($content['queries']); - } catch (QueryException) { - // don't crash! - return $content; - } - - $selections = Query::groupByType($parsed)['selections'] ?? []; - - if (! $hasWildcard) { - // check if any select includes a wildcard as we added one above - foreach ($selections as $select) { - if (\in_array('*', $select->getValues(), true)) { - $hasWildcard = true; - break; - } - } - } - - if ($hasWildcard && $model === 'databases.listDocuments') { - $relatedKeys = $this->getRelatedCollectionKeys(); - - if (! empty($relatedKeys)) { - $selects = \array_values(\array_unique(\array_merge(['*'], $relatedKeys))); - - // remove previous select queries - $parsed = \array_filter( - $parsed, - fn ($query) => $query->getMethod() !== Query::TYPE_SELECT - ); - - // add wildcard + relationship(s) selects - $parsed[] = Query::select($selects); - } - } - - $resolvedQueries = []; - foreach ($parsed as $query) { - // make em json encoded! - $resolvedQueries[] = $query->toString(); - } - - $content['queries'] = $resolvedQueries; - - return $content; - } - - /** - * Returns all relationship attribute keys in `key.*` format for use with `Query::select`. - */ - private function getRelatedCollectionKeys(): array - { - $dbForProject = $this->getDbForProject(); - - if ($dbForProject === null) { - return []; - } - - $databaseId = $this->getParamValue('databaseId'); - $collectionId = $this->getParamValue('collectionId'); - - if (empty($databaseId) || empty($collectionId)) { - return []; - } - - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - - $collection = $dbForProject->getDocument( - 'database_' . $database->getSequence(), - $collectionId - ); - - $attributes = $collection->getAttribute('attributes', []); - - return \array_values(\array_map( - fn ($attr) => $attr['key'] . '.*', - \array_filter( - $attributes, - fn ($attr) => ($attr['type'] ?? null) === Database::VAR_RELATIONSHIP - ) - )); - } } diff --git a/src/Appwrite/Utopia/Request/Filters/V20.php b/src/Appwrite/Utopia/Request/Filters/V20.php new file mode 100644 index 0000000000..8719cf7325 --- /dev/null +++ b/src/Appwrite/Utopia/Request/Filters/V20.php @@ -0,0 +1,124 @@ +manageSelectQueries($content, $model); + break; + } + return $content; + } + + /** + * From 1.8.x onward, related documents are no longer returned by default to improve performance. + * + * Use `Query::select(['related.*'])` for full documents or `Query::select(['related.key'])` for specific fields. + * + * This filter preserves 1.7.x behavior by including all related documents for backward compatibility with + * `listDocuments` and `getDocument` calls. + */ + protected function manageSelectQueries(array $content, string $model): array + { + $hasWildcard = false; + if (! isset($content['queries'])) { + $hasWildcard = true; + // only query, make it json encoded! + $content['queries'] = [Query::select(['*'])->toString()]; + } + + try { + $parsed = Query::parseQueries($content['queries']); + } catch (QueryException) { + // don't crash! + return $content; + } + + $selections = Query::groupByType($parsed)['selections'] ?? []; + + if (! $hasWildcard) { + // check if any select includes a wildcard as we added one above + foreach ($selections as $select) { + if (\in_array('*', $select->getValues(), true)) { + $hasWildcard = true; + break; + } + } + } + + if ($hasWildcard && $model === 'databases.listDocuments') { + $relatedKeys = $this->getRelatedCollectionKeys(); + + if (! empty($relatedKeys)) { + $selects = \array_values(\array_unique(\array_merge(['*'], $relatedKeys))); + + // remove previous select queries + $parsed = \array_filter( + $parsed, + fn ($query) => $query->getMethod() !== Query::TYPE_SELECT + ); + + // add wildcard + relationship(s) selects + $parsed[] = Query::select($selects); + } + } + + $resolvedQueries = []; + foreach ($parsed as $query) { + // make em json encoded! + $resolvedQueries[] = $query->toString(); + } + + $content['queries'] = $resolvedQueries; + + return $content; + } + + /** + * Returns all relationship attribute keys in `key.*` format for use with `Query::select`. + */ + private function getRelatedCollectionKeys(): array + { + $dbForProject = $this->getDbForProject(); + + if ($dbForProject === null) { + return []; + } + + $databaseId = $this->getParamValue('databaseId'); + $collectionId = $this->getParamValue('collectionId'); + + if (empty($databaseId) || empty($collectionId)) { + return []; + } + + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + + $collection = $dbForProject->getDocument( + 'database_' . $database->getSequence(), + $collectionId + ); + + $attributes = $collection->getAttribute('attributes', []); + + return \array_values(\array_map( + fn ($attr) => $attr['key'] . '.*', + \array_filter( + $attributes, + fn ($attr) => ($attr['type'] ?? null) === Database::VAR_RELATIONSHIP + ) + )); + } +} diff --git a/src/Appwrite/Utopia/Response/Filters/V20.php b/src/Appwrite/Utopia/Response/Filters/V20.php new file mode 100644 index 0000000000..c0ed8297cc --- /dev/null +++ b/src/Appwrite/Utopia/Response/Filters/V20.php @@ -0,0 +1,14 @@ +