diff --git a/app/controllers/general.php b/app/controllers/general.php index 8fc5a11503..4ca3d1dd69 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -25,6 +25,7 @@ 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\Request\Filters\V21 as RequestV21; +use Appwrite\Utopia\Request\Filters\V22 as RequestV22; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Filters\V16 as ResponseV16; use Appwrite\Utopia\Response\Filters\V17 as ResponseV17; @@ -924,6 +925,9 @@ App::init() if (version_compare($requestFormat, '1.9.0', '<')) { $request->addFilter(new RequestV21()); } + if (version_compare($requestFormat, '1.10.0', '<')) { + $request->addFilter(new RequestV22()); + } } $localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', '')); diff --git a/composer.lock b/composer.lock index acf4218e48..8d66df89c8 100644 --- a/composer.lock +++ b/composer.lock @@ -1589,16 +1589,16 @@ }, { "name": "open-telemetry/sem-conv", - "version": "1.37.0", + "version": "1.38.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sem-conv.git", - "reference": "8da7ec497c881e39afa6657d72586e27efbd29a1" + "reference": "e613bc640a407def4991b8a936a9b27edd9a3240" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/8da7ec497c881e39afa6657d72586e27efbd29a1", - "reference": "8da7ec497c881e39afa6657d72586e27efbd29a1", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/e613bc640a407def4991b8a936a9b27edd9a3240", + "reference": "e613bc640a407def4991b8a936a9b27edd9a3240", "shasum": "" }, "require": { @@ -1638,11 +1638,11 @@ ], "support": { "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/php", + "docs": "https://opentelemetry.io/docs/languages/php", "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-09-03T12:08:10+00:00" + "time": "2026-01-21T04:14:03+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -3903,12 +3903,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "240a123e57fe9677224b4314fff81ce0d22baebd" + "reference": "b1c88dd6f624026f9a39634870b3b933e0d5cf3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/240a123e57fe9677224b4314fff81ce0d22baebd", - "reference": "240a123e57fe9677224b4314fff81ce0d22baebd", + "url": "https://api.github.com/repos/utopia-php/database/zipball/b1c88dd6f624026f9a39634870b3b933e0d5cf3d", + "reference": "b1c88dd6f624026f9a39634870b3b933e0d5cf3d", "shasum": "" }, "require": { @@ -3953,7 +3953,7 @@ "issues": "https://github.com/utopia-php/database/issues", "source": "https://github.com/utopia-php/database/tree/joins8" }, - "time": "2026-01-19T11:25:09+00:00" + "time": "2026-01-21T09:24:33+00:00" }, { "name": "utopia-php/detector", @@ -5482,16 +5482,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.8.17", + "version": "1.8.18", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "1bc5a39bf87d3c2064f2f8d45fa712340338bc41" + "reference": "2775d5956a8bb65a94022255b2a68eb55f000abb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/1bc5a39bf87d3c2064f2f8d45fa712340338bc41", - "reference": "1bc5a39bf87d3c2064f2f8d45fa712340338bc41", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/2775d5956a8bb65a94022255b2a68eb55f000abb", + "reference": "2775d5956a8bb65a94022255b2a68eb55f000abb", "shasum": "" }, "require": { @@ -5527,9 +5527,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.8.17" + "source": "https://github.com/appwrite/sdk-generator/tree/1.8.18" }, - "time": "2026-01-19T12:13:41+00:00" + "time": "2026-01-20T16:16:38+00:00" }, { "name": "doctrine/annotations", diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/XList.php index e3dd46839a..6c7b3edf1f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/XList.php @@ -18,6 +18,7 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Query\Cursor; use Utopia\Platform\Action; use Utopia\Swoole\Response as SwooleResponse; +use Utopia\Validator; use Utopia\Validator\Boolean; use Utopia\Validator\Text; diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php index c5fd36c42b..6ef670f554 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php @@ -54,7 +54,7 @@ class Base extends Types Query::TYPE_NOT_TOUCHES, Query::TYPE_VECTOR_DOT, Query::TYPE_VECTOR_COSINE, - Query::TYPE_VECTOR_EUCLIDEAN + Query::TYPE_VECTOR_EUCLIDEAN, ]; /** diff --git a/src/Appwrite/Utopia/Request/Filters/V22.php b/src/Appwrite/Utopia/Request/Filters/V22.php index af72a2e4ee..235957939c 100644 --- a/src/Appwrite/Utopia/Request/Filters/V22.php +++ b/src/Appwrite/Utopia/Request/Filters/V22.php @@ -4,57 +4,27 @@ namespace Appwrite\Utopia\Request\Filters; use Appwrite\Extend\Exception; use Appwrite\Utopia\Request\Filter; +use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Query; class V22 extends Filter { - protected const CHAR_SINGLE_QUOTE = '\''; - protected const CHAR_DOUBLE_QUOTE = '"'; - protected const CHAR_COMMA = ','; - protected const CHAR_SPACE = ' '; - protected const CHAR_BRACKET_START = '['; - protected const CHAR_BRACKET_END = ']'; - protected const CHAR_PARENTHESES_START = '('; - protected const CHAR_PARENTHESES_END = ')'; - protected const CHAR_BACKSLASH = '\\'; - - // Convert 1.4 params to 1.5 public function parse(array $content, string $model): array { - switch ($model) { - case 'account.listIdentities': - case 'account.listLogs': - case 'databases.list': - case 'databases.listLogs': - case 'databases.listCollections': - case 'databases.listCollectionLogs': - case 'databases.listAttributes': - case 'databases.listIndexes': - case 'databases.listDocuments': - case 'databases.getDocument': - case 'databases.listDocumentLogs': - case 'functions.list': - case 'functions.listDeployments': - case 'functions.listExecutions': - case 'migrations.list': - case 'projects.list': - case 'proxy.listRules': - case 'storage.listBuckets': - case 'storage.listFiles': - case 'teams.list': - case 'teams.listMemberships': - case 'teams.listLogs': - case 'users.list': - case 'users.listLogs': - case 'users.listIdentities': - case 'vcs.listInstallations': - $content = $this->convertQueries($content); - break; + var_dump('========================='); + var_dump($model); + + if (isset($content['queries'])){ + var_dump('=== queries ==='); + var_dump($content['queries']); + $content = $this->convertSelectQueries($content); } + + var_dump('========================='); return $content; } - private function convertQueries(array $content): array + private function convertSelectQueries(array $content): array { var_dump('convertQueries'); @@ -62,18 +32,18 @@ class V22 extends Filter return $content; } + try { + $parsed = Query::parseQueries($content['queries']); + } catch (QueryException $e) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); + } + $queries = []; - /** @var Query $query */ - - foreach ($content['queries'] as $query) { + foreach ($parsed as $query) { try { - var_dump($query); - if ($query->getMethod() === 'select') { - $selects = $query->getValue(); - - foreach ($selects as $select) { + foreach ($query->getValues() as $select) { $queries[] = Query::select($select); } } else { @@ -85,7 +55,13 @@ class V22 extends Filter } } - $content['queries'] = $queries; + $resolvedQueries = []; + + foreach ($queries as $query) { + $resolvedQueries[] = $query->toString(); + } + + $content['queries'] = $resolvedQueries; return $content; } diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesConsoleClientTest.php b/tests/e2e/Services/Databases/Legacy/DatabasesConsoleClientTest.php index e81730411b..2ec6abf1f5 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesConsoleClientTest.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesConsoleClientTest.php @@ -94,6 +94,34 @@ class DatabasesConsoleClientTest extends Scope $this->assertEquals(201, $tvShows['headers']['status-code']); $this->assertEquals($tvShows['body']['name'], 'TvShows'); + /** + * Check select queries are disabled in Base class + */ + $list = $this->client->call(Client::METHOD_GET, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::select('name')->toString(), + ] + ]); + $this->assertEquals(400, $list['headers']['status-code']); + $this->assertEquals('Invalid `queries` param: Query method select not allowed', $list['body']['message']); + + /** + * Check allowed attribute filters + */ + $list = $this->client->call(Client::METHOD_GET, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::equal('enabled', [true])->toString(), + ] + ]); + $this->assertEquals(400, $list['headers']['status-code']); + $this->assertEquals('Invalid `queries` param: Invalid query: Attribute not found in schema: enabled', $list['body']['message']); + return ['moviesId' => $movies['body']['$id'], 'databaseId' => $databaseId, 'tvShowsId' => $tvShows['body']['$id']]; } diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 09d5ba2cf3..7c30f93140 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -433,6 +433,40 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(400, $response['headers']['status-code']); + + /** + * Test select queries + */ + /** + * Check old version selects converted to list of selects + */ + $list = $this->client->call(Client::METHOD_GET, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + '{"method":"select","values":["name", "$createdAt","enabled"]}', + ] + ]); + $this->assertEquals(400, $list['headers']['status-code']); + $this->assertEquals('Invalid `queries` param: Invalid query: Select queries attribute is empty', $list['body']['message']); + + $list = $this->client->call(Client::METHOD_GET, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-response-format' => '1.8.0', + ], $this->getHeaders()), [ + 'queries' => [ + '{"method":"select","values":["name", "$createdAt","enabled"]}', + ] + ]); + + var_dump('{"method":"select","values":["name", "$createdAt"]}'); + var_dump($list['databases'][0]['$createdAt']); + var_dump($list['databases'][0]['$updatedAt']); + $this->assertEquals(200, $list['headers']['status-code']); + $this->assertEquals(999, $list['headers']['status-code']); + return $data; }