From 27f69e3c09176ea7216ef4fbf01c6001dac0a59a Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 19 Aug 2022 00:09:34 +0000 Subject: [PATCH 01/17] Consolidate List User Logs API params Replace limit and offset params with a single queries param since the Queries V2 change now allows for different types of queries. --- app/controllers/api/users.php | 13 ++- composer.json | 5 +- composer.lock | 20 ++-- .../Validator/Queries/LimitOffsetQuery.php | 58 +++++++++ tests/e2e/Services/Users/UsersBase.php | 110 +++++++++++++++--- .../Queries/LimitOffsetQueryTest.php | 63 ++++++++++ 6 files changed, 244 insertions(+), 25 deletions(-) create mode 100644 src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetQuery.php create mode 100644 tests/unit/Utopia/Database/Validator/Queries/LimitOffsetQueryTest.php diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 71e6feb224..a708fe0e45 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -10,7 +10,9 @@ use Appwrite\Event\Audit as EventAudit; use Appwrite\Network\Validator\Email; use Appwrite\Stats\Stats; use Appwrite\Utopia\Database\Validator\CustomId; +use Appwrite\Utopia\Database\Validator\Queries; use Appwrite\Utopia\Database\Validator\Queries\Users; +use Appwrite\Utopia\Database\Validator\Queries\LimitOffsetQuery; use Appwrite\Utopia\Response; use Utopia\App; use Utopia\Audit\Audit; @@ -30,7 +32,6 @@ use Utopia\Database\Validator\Authorization; use Utopia\Validator\Assoc; use Utopia\Validator\WhiteList; use Utopia\Validator\Text; -use Utopia\Validator\Range; use Utopia\Validator\Boolean; use MaxMind\Db\Reader; @@ -306,14 +307,13 @@ App::get('/v1/users/:userId/logs') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->param('userId', '', new UID(), 'User ID.') - ->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) - ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true) + ->param('queries', [], new Queries(new LimitOffsetQuery(), strict: false), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') ->inject('locale') ->inject('geodb') ->inject('usage') - ->action(function (string $userId, int $limit, int $offset, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Stats $usage) { + ->action(function (string $userId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Stats $usage) { $user = $dbForProject->getDocument('users', $userId); @@ -321,6 +321,11 @@ App::get('/v1/users/:userId/logs') throw new Exception(Exception::USER_NOT_FOUND); } + $queries = Query::parseQueries($queries); + $grouped = Query::groupByType($queries); + $limit = $grouped['limit'] ?? 25; + $offset = $grouped['offset'] ?? 0; + $audit = new Audit($dbForProject); $logs = $audit->getLogsByUser($user->getId(), $limit, $offset); diff --git a/composer.json b/composer.json index fee2f93c96..0aa9f510f2 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "0.6.*", "utopia-php/cli": "0.13.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.22.*", + "utopia-php/database": "0.23.0 as 0.22.0", "utopia-php/locale": "0.4.*", "utopia-php/registry": "0.5.*", "utopia-php/preloader": "0.2.*", @@ -88,6 +88,9 @@ "ext-phpiredis": "*" }, "config": { + "optimize-autoloader": true, + "allow-plugins": false, + "preferred-install": "dist", "platform": { "php": "8.0" } diff --git a/composer.lock b/composer.lock index 2ce4203494..c4b157f5ec 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "175fe4abafd8bde4053b91eea905c328", + "content-hash": "1858c544a5b5ae59bc6ff146a375a683", "packages": [ { "name": "adhocore/jwt", @@ -2052,16 +2052,16 @@ }, { "name": "utopia-php/database", - "version": "0.22.0", + "version": "0.23.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "22c45ae83612e907203b7571cd8e3115ae3ae4c5" + "reference": "e4a23f8e26a3d96f292e549cebe0ff6937ec5d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/22c45ae83612e907203b7571cd8e3115ae3ae4c5", - "reference": "22c45ae83612e907203b7571cd8e3115ae3ae4c5", + "url": "https://api.github.com/repos/utopia-php/database/zipball/e4a23f8e26a3d96f292e549cebe0ff6937ec5d71", + "reference": "e4a23f8e26a3d96f292e549cebe0ff6937ec5d71", "shasum": "" }, "require": { @@ -2110,9 +2110,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.22.0" + "source": "https://github.com/utopia-php/database/tree/0.23.0" }, - "time": "2022-08-17T12:55:37+00:00" + "time": "2022-08-18T19:08:33+00:00" }, { "name": "utopia-php/domains", @@ -5354,6 +5354,12 @@ "version": "9999999-dev", "alias": "0.19.5", "alias_normalized": "0.19.5.0" + }, + { + "package": "utopia-php/database", + "version": "0.23.0.0", + "alias": "0.22.0", + "alias_normalized": "0.22.0.0" } ], "minimum-stability": "stable", diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetQuery.php b/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetQuery.php new file mode 100644 index 0000000000..d22c610542 --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetQuery.php @@ -0,0 +1,58 @@ +maxLimit = $maxLimit; + $this->maxOffset = $maxOffset; + } + + /** + * Is valid. + * + * Returns true if method is limit or offset and values are within range. + * + * @param Query $value + * + * @return bool + */ + public function isValid($query): bool + { + // Validate method + $method = $query->getMethod(); + switch ($method) { + case Query::TYPE_LIMIT: + $limit = $query->getValue(); + return $this->isValidLimit($limit); + + case Query::TYPE_OFFSET: + $offset = $query->getValue(); + return $this->isValidOffset($offset); + + default: + $this->message = 'Query method invalid: ' . $method; + return false; + } + } +} diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 1d3f7412a0..321f4f8e42 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -3,7 +3,6 @@ namespace Tests\E2E\Services\Users; use Tests\E2E\Client; -use Utopia\Database\Database; use Utopia\Database\ID; trait UsersBase @@ -673,26 +672,44 @@ trait UsersBase /** * Test for SUCCESS */ - $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + $i = 0; + do { + $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $i++; + } while ($logs['body']['total'] === 0 && $i < 1000); $this->assertEquals($logs['headers']['status-code'], 200); - $this->assertIsArray($logs['body']['logs']); - $this->assertIsNumeric($logs['body']['total']); + $this->assertCount(1, $logs['body']['logs']); + $this->assertEquals(1, $logs['body']['total']); + $this->assertIsArray($logs['body']['logs'][0]); $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'limit' => 1 + 'queries' => ['limit(1)'] ]); $this->assertEquals($logs['headers']['status-code'], 200); $this->assertIsArray($logs['body']['logs']); - $this->assertLessThanOrEqual(1, count($logs['body']['logs'])); - $this->assertIsNumeric($logs['body']['total']); + $this->assertCount(1, $logs['body']['logs']); + $this->assertEquals(1, $logs['body']['total']); + + $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => ['limit(0)'] + ]); + + $this->assertEquals($logs['headers']['status-code'], 200); + $this->assertIsArray($logs['body']['logs']); + $this->assertCount(0, $logs['body']['logs']); + $this->assertEquals(1, $logs['body']['total']); $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ 'content-type' => 'application/json', @@ -703,7 +720,8 @@ trait UsersBase $this->assertEquals($logs['headers']['status-code'], 200); $this->assertIsArray($logs['body']['logs']); - $this->assertIsNumeric($logs['body']['total']); + $this->assertCount(0, $logs['body']['logs']); + $this->assertEquals(1, $logs['body']['total']); $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ 'content-type' => 'application/json', @@ -717,8 +735,74 @@ trait UsersBase $this->assertEquals($logs['headers']['status-code'], 200); $this->assertIsArray($logs['body']['logs']); - $this->assertLessThanOrEqual(1, count($logs['body']['logs'])); - $this->assertIsNumeric($logs['body']['total']); + $this->assertCount(0, $logs['body']['logs']); + $this->assertEquals(1, $logs['body']['total']); + + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => ['limit(-1)'] + ]); + + $this->assertEquals($response['headers']['status-code'], 400); + + $response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => ['limit(101)'] + ]); + + $this->assertEquals($response['headers']['status-code'], 400); + + $response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => ['offset(-1)'] + ]); + + $this->assertEquals($response['headers']['status-code'], 400); + + $response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => ['offset(5001)'] + ]); + + $this->assertEquals($response['headers']['status-code'], 400); + + $response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => ['equal("$id", "asdf")'] + ]); + + $this->assertEquals($response['headers']['status-code'], 400); + + $response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => ['orderAsc("$id")'] + ]); + + $this->assertEquals($response['headers']['status-code'], 400); + + $response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => ['cursorAsc("$id")'] + ]); + + $this->assertEquals($response['headers']['status-code'], 400); } /** diff --git a/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetQueryTest.php b/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetQueryTest.php new file mode 100644 index 0000000000..d749dc7292 --- /dev/null +++ b/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetQueryTest.php @@ -0,0 +1,63 @@ +validator = new LimitOffsetQuery(); + } + + public function tearDown(): void + { + } + + public function testValue(): void + { + // Test for Success + $this->assertEquals($this->validator->isValid(Query::limit(1)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(0)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(100)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(1)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(0)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(5000)), true, $this->validator->getDescription()); + + // Test for Failure + $this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); + } + + public function testValues(): void + { + + $validator = new Queries($this->validator, strict: false); + + // Test for Success + $this->assertEquals($validator->isValid(['limit(1)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['limit(0)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['limit(100)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(1)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(0)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(5000)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['limit(25)', 'offset(25)']), true, $validator->getDescription()); + + // Test for Failure + $this->assertEquals($validator->isValid(['limit(-1)']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['limit(101)']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(-1)']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(5001)']), false, $validator->getDescription()); + } +} From 3890dea35b0fffd2097a5c2e7d85692943cbd825 Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 19 Aug 2022 23:20:22 +0000 Subject: [PATCH 02/17] Add additional Validators for Queries --- app/controllers/api/users.php | 2 +- composer.json | 3 - composer.lock | 10 +- .../Database/Validator/IndexedQueries.php | 151 ++++++++++++++++++ .../Utopia/Database/Validator/Queries.php | 103 ++++++++++-- .../LimitOffsetCursorFilterOrderQuery.php | 67 ++++++++ .../Queries/LimitOffsetCursorFilterQuery.php | 137 ++++++++++++++++ .../Queries/LimitOffsetCursorQuery.php | 64 ++++++++ .../Validator/Queries/LimitOffsetQuery.php | 65 +++++++- .../Database/Validator/Queries/Users.php | 3 + .../LimitOffsetCursorFilterOrderQueryTest.php | 94 +++++++++++ .../LimitOffsetCursorFilterQueryTest.php | 88 ++++++++++ .../Queries/LimitOffsetCursorQueryTest.php | 74 +++++++++ .../Queries/LimitOffsetQueryTest.php | 17 +- 14 files changed, 846 insertions(+), 32 deletions(-) create mode 100644 src/Appwrite/Utopia/Database/Validator/IndexedQueries.php create mode 100644 src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterOrderQuery.php create mode 100644 src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterQuery.php create mode 100644 src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorQuery.php create mode 100644 tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterOrderQueryTest.php create mode 100644 tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterQueryTest.php create mode 100644 tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorQueryTest.php diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 10526c835d..87a8959f91 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -574,7 +574,7 @@ App::get('/v1/users/:userId/logs') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->param('userId', '', new UID(), 'User ID.') - ->param('queries', [], new Queries(new LimitOffsetQuery(), strict: false), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true) + ->param('queries', [], new Queries(new LimitOffsetQuery()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') ->inject('locale') diff --git a/composer.json b/composer.json index 6b121ccea6..e90bca3182 100644 --- a/composer.json +++ b/composer.json @@ -88,9 +88,6 @@ "ext-phpiredis": "*" }, "config": { - "optimize-autoloader": true, - "allow-plugins": false, - "preferred-install": "dist", "platform": { "php": "8.0" } diff --git a/composer.lock b/composer.lock index 3c03182760..eb5849095e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5d5b72c7940a8376e187cce1b33d327a", + "content-hash": "0e850206a924d2a48861ecd290f59bc0", "packages": [ { "name": "adhocore/jwt", @@ -5354,12 +5354,6 @@ "version": "9999999-dev", "alias": "0.19.5", "alias_normalized": "0.19.5.0" - }, - { - "package": "utopia-php/database", - "version": "0.23.0.0", - "alias": "0.22.0", - "alias_normalized": "0.22.0.0" } ], "minimum-stability": "stable", @@ -5389,5 +5383,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } diff --git a/src/Appwrite/Utopia/Database/Validator/IndexedQueries.php b/src/Appwrite/Utopia/Database/Validator/IndexedQueries.php new file mode 100644 index 0000000000..2f9543aab7 --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/IndexedQueries.php @@ -0,0 +1,151 @@ +getAttribute('status') === 'available'; + }); + + $this->attributes = $attributes; + + $this->indexes[] = new Document([ + 'type' => Database::INDEX_UNIQUE, + 'attributes' => ['$id'] + ]); + + $this->indexes[] = new Document([ + 'type' => Database::INDEX_KEY, + 'attributes' => ['$createdAt'] + ]); + + $this->indexes[] = new Document([ + 'type' => Database::INDEX_KEY, + 'attributes' => ['$updatedAt'] + ]); + + foreach ($indexes ?? [] as $index) { + $this->indexes[] = $index; + } + + parent::__construct($validator); + } + + /** + * Check if indexed array $indexes matches $queries + * + * @param array $indexes + * @param array $queries + * + * @return bool + */ + protected function arrayMatch(array $indexes, array $queries): bool + { + // Check the count of indexes first for performance + if (count($queries) !== count($indexes)) { + return false; + } + + // Sort them for comparison, the order is not important here anymore. + sort($indexes, SORT_STRING); + sort($queries, SORT_STRING); + + // Only matching arrays will have equal diffs in both directions + if (array_diff_assoc($indexes, $queries) !== array_diff_assoc($queries, $indexes)) { + return false; + } + + return true; + } + + /** + * Is valid. + * + * Returns false if: + * 1. any query in $value is invalid based on $validator + * 2. there is no index with an exact match of the filters + * 3. there is no index with an exact match of the order attributes + * + * Otherwise, returns true. + * + * @param mixed $value + * @return bool + */ + public function isValid($value): bool + { + if (!$this->isValid($value)) { + return false; + } + + $queries = Query::parseQueries($value); + $grouped = Query::groupByType($queries); + /** @var Query[] */ $filters = $grouped['filters']; + /** @var string[] */ $orderAttributes = $grouped['orderAttributes']; + + // Check filter queries for exact index match + if (count($filters) > 0) { + $filtersByAttribute = []; + foreach ($filters as $filter) { + $filtersByAttribute[$filter->getAttribute()] = $filter->getMethod(); + } + + $found = null; + + foreach ($this->indexes as $index) { + if ($this->arrayMatch($index->getAttribute('attributes'), array_keys($filtersByAttribute))) { + $found = $index; + } + } + + if (!$found) { + $this->message = 'Index not found: ' . implode(",", array_keys($filtersByAttribute)); + return false; + } + + // search method requires fulltext index + if (in_array(Query::TYPE_SEARCH, array_values($filtersByAttribute)) && $found['type'] !== Database::INDEX_FULLTEXT) { + $this->message = 'Search method requires fulltext index: ' . implode(",", array_keys($filtersByAttribute)); + return false; + } + } + + // Check order attributes for exact index match + $validator = new OrderAttributes($this->attributes, $this->indexes, true); + if (count($orderAttributes) > 0 && !$validator->isValid($orderAttributes)) { + $this->message = $validator->getDescription(); + return false; + } + + return true; + } +} diff --git a/src/Appwrite/Utopia/Database/Validator/Queries.php b/src/Appwrite/Utopia/Database/Validator/Queries.php index 8ea70231d5..cc675429e4 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries.php @@ -2,28 +2,103 @@ namespace Appwrite\Utopia\Database\Validator; +use Utopia\Validator; use Utopia\Database\Document; -use Utopia\Database\Validator\Queries as ValidatorQueries; +use Utopia\Database\Validator\Query as QueryValidator; +use Utopia\Database\Query; -class Queries extends ValidatorQueries +class Queries extends Validator { /** - * Expression constructor + * @var string + */ + protected $message = 'Invalid queries'; + + /** + * @var QueryValidator + */ + protected $validator; + + /** + * Queries constructor * - * This Queries Validator that filters indexes for only available indexes - * - * @param QueryValidator $validator - * @param Document[] $attributes - * @param Document[] $indexes + * @param Validator $validator used to validate each query + * @param Document[] $attributes allowed attributes to be queried + * @param Document[] $indexes available for strict query matching * @param bool $strict */ - public function __construct($validator, $attributes = [], $indexes = [], $strict = true) + public function __construct(Validator $validator) { - // Remove failed/stuck/processing indexes - $availableIndexes = \array_filter($indexes, function ($index) { - return $index->getAttribute('status') === 'available'; - }); + $this->validator = $validator; + } - parent::__construct($validator, $attributes, $availableIndexes, $strict); + /** + * Get Description. + * + * Returns validator description + * + * @return string + */ + public function getDescription(): string + { + return $this->message; + } + + /** + * Is valid. + * + * Returns false if: + * 1. any query in $value is invalid based on $validator + * + * Otherwise, returns true. + * + * @param mixed $value + * @return bool + */ + public function isValid($value): bool + { + $queries = []; + foreach ($value as $query) { + if (!$query instanceof Query) { + try { + $query = Query::parse($query); + } catch (\Throwable $th) { + $this->message = 'Invalid query: ${query}'; + return false; + } + } + + if (!$this->validator->isValid($query)) { + $this->message = 'Query not valid: ' . $this->validator->getDescription(); + return false; + } + + $queries[] = $query; + } + + return true; + } + /** + * Is array + * + * Function will return true if object is array. + * + * @return bool + */ + public function isArray(): bool + { + return true; + } + + /** + * Get Type + * + * Returns validator type. + * + * @return string + */ + public function getType(): string + { + return self::TYPE_OBJECT; } } diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterOrderQuery.php b/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterOrderQuery.php new file mode 100644 index 0000000000..3a8459fa47 --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterOrderQuery.php @@ -0,0 +1,67 @@ +schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); + } + + $this->maxValuesCount = $maxValuesCount; + + parent::__construct($maxLimit, $maxOffset); + } + + /** + * Is valid. + * + * Returns true if: + * 1. method is limit or offset and values are within range + * 2. method is cursorBefore or cursorAfter and value is not null + * 3. method is a filter method, attribute exists, and value matches attribute type + * 4. method is orderAsc or orderDesc and attribute exists or is empty string + * + * Otherwise, returns false + * + * @param Query $value + * + * @return bool + */ + public function isValid($query): bool + { + $method = $query->getMethod(); + $attribute = $query->getAttribute(); + + if ($method === Query::TYPE_ORDERASC || $method === Query::TYPE_ORDERDESC) { + if ($attribute === '') { + return true; + } + return $this->isValidAttribute($attribute); + } + + return parent::isValid($query); + } +} diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterQuery.php b/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterQuery.php new file mode 100644 index 0000000000..71da608709 --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterQuery.php @@ -0,0 +1,137 @@ +schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); + } + + $this->maxValuesCount = $maxValuesCount; + + parent::__construct($maxLimit, $maxOffset); + } + + protected function isValidAttribute($attribute): bool + { + // Search for attribute in schema + if (!isset($this->schema[$attribute])) { + $this->message = 'Attribute not found in schema: ' . $attribute; + return false; + } + + return true; + } + + protected function isValidAttributeAndValues(string $attribute, array $values): bool + { + if (!$this->isValidAttribute($attribute)) { + return false; + } + + $attributeSchema = $this->schema[$attribute]; + + if (count($values) > $this->maxValuesCount) { + $this->message = 'Query on attribute has greater than ' . $this->maxValuesCount . ' values: ' . $attribute; + return false; + } + + // Extract the type of desired attribute from collection $schema + $attributeType = $attributeSchema['type']; + + foreach ($values as $value) { + $condition = match ($attributeType) { + Database::VAR_DATETIME => gettype($value) === Database::VAR_STRING, + default => gettype($value) === $attributeType + }; + + if (!$condition) { + $this->message = 'Query type does not match expected: ' . $attributeType; + return false; + } + } + + return true; + } + + protected function isValidContains(string $attribute, array $values): bool + { + if (!$this->isValidAttributeAndValues($attribute, $values)) { + return false; + } + + $attributeSchema = $this->schema[$attribute]; + + // Contains method only supports array attributes + if (!$attributeSchema['array']) { + $this->message = 'Query method only supported on array attributes: ' . Query::TYPE_CONTAINS; + return false; + } + + return true; + } + + /** + * Is valid. + * + * Returns true if: + * 1. method is limit or offset and values are within range + * 2. method is cursorBefore or cursorAfter and value is not null + * 3. method is a filter method, attribute exists, and value matches attribute type + * + * Otherwise, returns false + * + * @param Query $value + * + * @return bool + */ + public function isValid($query): bool + { + // Validate method + $method = $query->getMethod(); + $attribute = $query->getAttribute(); + + switch ($method) { + case Query::TYPE_CONTAINS: + $values = $query->getValues(); + return $this->isValidContains($attribute, $values); + + case Query::TYPE_EQUAL: + case Query::TYPE_NOTEQUAL: + case Query::TYPE_LESSER: + case Query::TYPE_LESSEREQUAL: + case Query::TYPE_GREATER: + case Query::TYPE_GREATEREQUAL: + case Query::TYPE_SEARCH: + $values = $query->getValues(); + return $this->isValidAttributeAndValues($attribute, $values); + + default: + return parent::isValid($query); + } + } +} diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorQuery.php b/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorQuery.php new file mode 100644 index 0000000000..fb45aaf2e1 --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorQuery.php @@ -0,0 +1,64 @@ +isValid($cursor)) { + return true; + } + + $this->message = 'Invalid cursor: ' . $validator->getDescription(); + return false; + } + + /** + * Is valid. + * + * Returns true if: + * 1. method is limit or offset and values are within range + * 2. method is cursorBefore or cursorAfter and value is not null. + * + * Otherwise, returns false + * + * @param Query $value + * + * @return bool + */ + public function isValid($query): bool + { + // Validate method + $method = $query->getMethod(); + + if ($method === Query::TYPE_CURSORAFTER || $method === Query::TYPE_CURSORBEFORE) { + $cursor = $query->getValue(); + return $this->isValidCursor($cursor); + } + + return parent::isValid($query); + } +} diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetQuery.php b/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetQuery.php index d22c610542..e11590757e 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetQuery.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetQuery.php @@ -3,9 +3,10 @@ namespace Appwrite\Utopia\Database\Validator\Queries; use Utopia\Database\Query; -use Utopia\Database\Validator\Query as QueryValidator; +use Utopia\Validator\Range; +use Utopia\Validator; -class LimitOffsetQuery extends QueryValidator +class LimitOffsetQuery extends Validator { /** * @var string @@ -28,11 +29,45 @@ class LimitOffsetQuery extends QueryValidator $this->maxOffset = $maxOffset; } + /** + * Get Description. + * + * Returns validator description + * + * @return string + */ + public function getDescription(): string + { + return $this->message; + } + + protected function isValidLimit($limit): bool + { + $validator = new Range(0, $this->maxLimit); + if ($validator->isValid($limit)) { + return true; + } + + $this->message = 'Invalid limit: ' . $validator->getDescription(); + return false; + } + + protected function isValidOffset($offset): bool + { + $validator = new Range(0, $this->maxOffset); + if ($validator->isValid($offset)) { + return true; + } + + $this->message = 'Invalid offset: ' . $validator->getDescription(); + return false; + } + /** * Is valid. * * Returns true if method is limit or offset and values are within range. - * + * * @param Query $value * * @return bool @@ -55,4 +90,28 @@ class LimitOffsetQuery extends QueryValidator return false; } } + + /** + * Is array + * + * Function will return true if object is array. + * + * @return bool + */ + public function isArray(): bool + { + return false; + } + + /** + * Get Type + * + * Returns validator type. + * + * @return string + */ + public function getType(): string + { + return self::TYPE_OBJECT; + } } diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Users.php b/src/Appwrite/Utopia/Database/Validator/Queries/Users.php index fcef3f9928..59373c2cab 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Users.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Users.php @@ -7,6 +7,9 @@ use Appwrite\Utopia\Database\Validator\Queries\Collection; class Users extends Collection { public const ALLOWED_ATTRIBUTES = [ + '$id', + '$createdAt', + '$updatedAt', 'name', 'email', 'phone', diff --git a/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterOrderQueryTest.php b/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterOrderQueryTest.php new file mode 100644 index 0000000000..c46911b30e --- /dev/null +++ b/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterOrderQueryTest.php @@ -0,0 +1,94 @@ +validator = new LimitOffsetCursorFilterOrderQuery( + attributes: [ + new Document([ + 'key' => 'attr', + 'type' => Database::VAR_STRING, + 'array' => false, + ]), + ], + ); + } + + public function tearDown(): void + { + } + + public function testValue(): void + { + // Test for Success + $this->assertEquals($this->validator->isValid(Query::limit(1)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(0)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(100)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(1)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(0)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(5000)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderAsc('')), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderDesc('')), true, $this->validator->getDescription()); + + // Test for Failure + $this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::equal('dne', ['v'])), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::equal('', ['v'])), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderDesc('dne')), false, $this->validator->getDescription()); + } + + public function testValues(): void + { + + $validator = new Queries($this->validator); + + // Test for Success + $this->assertEquals($validator->isValid(['limit(1)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['limit(0)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['limit(100)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(1)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(0)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(5000)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['limit(25)', 'offset(25)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['cursorAfter("asdf")']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['cursorBefore("asdf")']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['equal("attr", "v")']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['orderAsc("attr")']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['orderAsc("")']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['orderDesc("attr")']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['orderDesc("")']), true, $validator->getDescription()); + + // Test for Failure + $this->assertEquals($validator->isValid(['limit(-1)']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['limit(101)']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(-1)']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(5001)']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['equal("dne", "v")']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['equal("", "v")']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['orderDesc("dne")']), false, $validator->getDescription()); + } +} diff --git a/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterQueryTest.php b/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterQueryTest.php new file mode 100644 index 0000000000..1ae43ae37f --- /dev/null +++ b/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterQueryTest.php @@ -0,0 +1,88 @@ +validator = new LimitOffsetCursorFilterQuery( + attributes: [ + new Document([ + 'key' => 'attr', + 'type' => Database::VAR_STRING, + 'array' => false, + ]), + ], + ); + } + + public function tearDown(): void + { + } + + public function testValue(): void + { + // Test for Success + $this->assertEquals($this->validator->isValid(Query::limit(1)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(0)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(100)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(1)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(0)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(5000)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), true, $this->validator->getDescription()); + + // Test for Failure + $this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::equal('dne', ['v'])), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::equal('', ['v'])), false, $this->validator->getDescription()); + } + + public function testValues(): void + { + + $validator = new Queries($this->validator); + + // Test for Success + $this->assertEquals($validator->isValid(['limit(1)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['limit(0)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['limit(100)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(1)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(0)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(5000)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['limit(25)', 'offset(25)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['cursorAfter("asdf")']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['cursorBefore("asdf")']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['equal("attr", "v")']), true, $validator->getDescription()); + + // Test for Failure + $this->assertEquals($validator->isValid(['limit(-1)']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['limit(101)']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(-1)']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(5001)']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['orderAsc("attr")']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['orderDesc("attr")']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['equal("dne", "v")']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['equal("", "v")']), false, $validator->getDescription()); + } +} diff --git a/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorQueryTest.php b/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorQueryTest.php new file mode 100644 index 0000000000..77e6a1e257 --- /dev/null +++ b/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorQueryTest.php @@ -0,0 +1,74 @@ +validator = new LimitOffsetCursorQuery(); + } + + public function tearDown(): void + { + } + + public function testValue(): void + { + // Test for Success + $this->assertEquals($this->validator->isValid(Query::limit(1)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(0)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(100)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(1)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(0)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(5000)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), true, $this->validator->getDescription()); + + // Test for Failure + $this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription()); + } + + public function testValues(): void + { + + $validator = new Queries($this->validator); + + // Test for Success + $this->assertEquals($validator->isValid(['limit(1)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['limit(0)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['limit(100)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(1)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(0)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(5000)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['limit(25)', 'offset(25)']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['cursorAfter("asdf")']), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(['cursorBefore("asdf")']), true, $validator->getDescription()); + + // Test for Failure + $this->assertEquals($validator->isValid(['limit(-1)']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['limit(101)']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(-1)']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['offset(5001)']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['equal("attr", "v")']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['orderAsc("attr")']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['orderDesc("attr")']), false, $validator->getDescription()); + } +} diff --git a/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetQueryTest.php b/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetQueryTest.php index d749dc7292..724a1e5939 100644 --- a/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetQueryTest.php +++ b/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetQueryTest.php @@ -5,12 +5,13 @@ namespace Tests\Unit\Utopia\Database\Validator\Queries; use Appwrite\Utopia\Database\Validator\Queries; use Appwrite\Utopia\Database\Validator\Queries\LimitOffsetQuery; use Utopia\Database\Query; +use Utopia\Validator; use PHPUnit\Framework\TestCase; class LimitOffsetQueryTest extends TestCase { /** - * @var Key + * @var Validator */ protected $validator = null; @@ -38,13 +39,18 @@ class LimitOffsetQueryTest extends TestCase $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription()); } public function testValues(): void { - $validator = new Queries($this->validator, strict: false); - + $validator = new Queries($this->validator); + // Test for Success $this->assertEquals($validator->isValid(['limit(1)']), true, $validator->getDescription()); $this->assertEquals($validator->isValid(['limit(0)']), true, $validator->getDescription()); @@ -59,5 +65,10 @@ class LimitOffsetQueryTest extends TestCase $this->assertEquals($validator->isValid(['limit(101)']), false, $validator->getDescription()); $this->assertEquals($validator->isValid(['offset(-1)']), false, $validator->getDescription()); $this->assertEquals($validator->isValid(['offset(5001)']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['cursorAfter("asdf")']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['cursorBefore("asdf")']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['equal("attr", "v")']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['orderAsc("attr")']), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(['orderDesc("attr")']), false, $validator->getDescription()); } } From 146cb6a0d8694ae8ff7b9ecdd776425642fd0cba Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 19 Aug 2022 23:31:02 +0000 Subject: [PATCH 03/17] Fix list user logs tests --- .../Queries/LimitOffsetCursorQuery.php | 2 +- tests/e2e/Services/Users/UsersBase.php | 51 ++++++------------- 2 files changed, 16 insertions(+), 37 deletions(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorQuery.php b/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorQuery.php index fb45aaf2e1..57f7370a54 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorQuery.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorQuery.php @@ -41,7 +41,7 @@ class LimitOffsetCursorQuery extends LimitOffsetQuery * * Returns true if: * 1. method is limit or offset and values are within range - * 2. method is cursorBefore or cursorAfter and value is not null. + * 2. method is cursorBefore or cursorAfter and value is not null * * Otherwise, returns false * diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index dcbeb389ca..f2e498366b 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -971,71 +971,50 @@ trait UsersBase /** * Test for SUCCESS */ - $i = 0; - do { - $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $i++; - } while ($logs['body']['total'] === 0 && $i < 1000); + $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); $this->assertEquals($logs['headers']['status-code'], 200); - $this->assertCount(1, $logs['body']['logs']); - $this->assertEquals(1, $logs['body']['total']); - $this->assertIsArray($logs['body']['logs'][0]); + $this->assertIsArray($logs['body']['logs']); + $this->assertIsNumeric($logs['body']['total']); $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(1)'] + 'limit' => 1 ]); $this->assertEquals($logs['headers']['status-code'], 200); $this->assertIsArray($logs['body']['logs']); - $this->assertCount(1, $logs['body']['logs']); - $this->assertEquals(1, $logs['body']['total']); + $this->assertLessThanOrEqual(1, count($logs['body']['logs'])); + $this->assertIsNumeric($logs['body']['total']); $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['limit(0)'] + 'offset' => 1 ]); $this->assertEquals($logs['headers']['status-code'], 200); $this->assertIsArray($logs['body']['logs']); - $this->assertCount(0, $logs['body']['logs']); - $this->assertEquals(1, $logs['body']['total']); + $this->assertIsNumeric($logs['body']['total']); $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['offset(1)'] + 'offset' => 1, + 'limit' => 1 ]); $this->assertEquals($logs['headers']['status-code'], 200); $this->assertIsArray($logs['body']['logs']); - $this->assertCount(0, $logs['body']['logs']); - $this->assertEquals(1, $logs['body']['total']); - - $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - 'offset(1)', - 'limit(1)', - ] - ]); - - $this->assertEquals($logs['headers']['status-code'], 200); - $this->assertIsArray($logs['body']['logs']); - $this->assertCount(0, $logs['body']['logs']); - $this->assertEquals(1, $logs['body']['total']); + $this->assertLessThanOrEqual(1, count($logs['body']['logs'])); + $this->assertIsNumeric($logs['body']['total']); /** * Test for FAILURE From 2bde7aa02b0ee2c3e9aeb4add65c619edf1223ee Mon Sep 17 00:00:00 2001 From: Steven Date: Sat, 20 Aug 2022 00:15:18 +0000 Subject: [PATCH 04/17] Fix databases tests --- app/controllers/api/databases.php | 7 +++---- .../Utopia/Database/Validator/IndexedQueries.php | 12 ++++++++++-- .../Validator/Queries/LimitOffsetCursorQuery.php | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index f1f3356ff1..48246646b1 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -25,7 +25,6 @@ use Utopia\Database\Adapter\MariaDB; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Key; use Utopia\Database\Validator\Permissions; -use Utopia\Database\Validator\Query as QueryValidator; use Utopia\Database\Validator\Structure; use Utopia\Database\Validator\UID; use Utopia\Database\Exception\Authorization as AuthorizationException; @@ -38,8 +37,8 @@ use Appwrite\Network\Validator\Email; use Appwrite\Network\Validator\IP; use Appwrite\Network\Validator\URL; use Appwrite\Utopia\Database\Validator\CustomId; -use Appwrite\Utopia\Database\Validator\Queries as QueriesValidator; -use Appwrite\Utopia\Database\Validator\OrderAttributes; +use Appwrite\Utopia\Database\Validator\IndexedQueries; +use Appwrite\Utopia\Database\Validator\Queries\LimitOffsetCursorFilterOrderQuery; use Appwrite\Utopia\Response; use Appwrite\Detector\Detector; use Appwrite\Event\Database as EventDatabase; @@ -2062,7 +2061,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') if (!empty($allQueries)) { $attributes = $collection->getAttribute('attributes', []); - $validator = new QueriesValidator(new QueryValidator($attributes), $attributes, $collection->getAttribute('indexes', []), true); + $validator = new IndexedQueries(new LimitOffsetCursorFilterOrderQuery(attributes: $attributes), $attributes, $collection->getAttribute('indexes', [])); if (!$validator->isValid($allQueries)) { throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); } diff --git a/src/Appwrite/Utopia/Database/Validator/IndexedQueries.php b/src/Appwrite/Utopia/Database/Validator/IndexedQueries.php index 2f9543aab7..f1f3e62eb9 100644 --- a/src/Appwrite/Utopia/Database/Validator/IndexedQueries.php +++ b/src/Appwrite/Utopia/Database/Validator/IndexedQueries.php @@ -103,11 +103,19 @@ class IndexedQueries extends Queries */ public function isValid($value): bool { - if (!$this->isValid($value)) { + if (!parent::isValid($value)) { return false; } - $queries = Query::parseQueries($value); + $queries = []; + foreach ($value as $query) { + if (!$query instanceof Query) { + $query = Query::parse($query); + } + + $queries[] = $query; + } + $grouped = Query::groupByType($queries); /** @var Query[] */ $filters = $grouped['filters']; /** @var string[] */ $orderAttributes = $grouped['orderAttributes']; diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorQuery.php b/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorQuery.php index 57f7370a54..f74d41474c 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorQuery.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorQuery.php @@ -44,7 +44,7 @@ class LimitOffsetCursorQuery extends LimitOffsetQuery * 2. method is cursorBefore or cursorAfter and value is not null * * Otherwise, returns false - * + * * @param Query $value * * @return bool From e70eb61717412cdb17f6da00ba48dac8957cb6a7 Mon Sep 17 00:00:00 2001 From: Steven Date: Sat, 20 Aug 2022 00:47:10 +0000 Subject: [PATCH 05/17] Fix bug with list documents and new queries --- app/controllers/api/databases.php | 41 ++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 48246646b1..7efd9fac8e 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -2027,15 +2027,34 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') throw new Exception(Exception::USER_UNAUTHORIZED); } - $filterQueries = \array_map(function ($query) { - $query = Query::parse($query); - - if (\count($query->getValues()) > 100) { - throw new Exception(Exception::GENERAL_QUERY_LIMIT_EXCEEDED, "You cannot use more than 100 query values on attribute '{$query->getAttribute()}'"); + if (!empty($queries)) { + $attributes = array_merge( + $collection->getAttribute('attributes', []), + [ + new Document([ + 'key' => '$id', + 'type' => Database::VAR_STRING, + 'array' => false, + ]), + new Document([ + 'key' => '$createdAt', + 'type' => Database::VAR_DATETIME, + 'array' => false, + ]), + new Document([ + 'key' => '$updatedAt', + 'type' => Database::VAR_DATETIME, + 'array' => false, + ]), + ] + ); + $validator = new IndexedQueries(new LimitOffsetCursorFilterOrderQuery(attributes: $attributes), $attributes, $collection->getAttribute('indexes', [])); + if (!$validator->isValid($queries)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); } + } - return $query; - }, $queries); + $filterQueries = Query::parseQueries($queries); $otherQueries = []; $otherQueries[] = Query::limit($limit); @@ -2059,14 +2078,6 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') $allQueries = \array_merge($filterQueries, $otherQueries); - if (!empty($allQueries)) { - $attributes = $collection->getAttribute('attributes', []); - $validator = new IndexedQueries(new LimitOffsetCursorFilterOrderQuery(attributes: $attributes), $attributes, $collection->getAttribute('indexes', [])); - if (!$validator->isValid($allQueries)) { - throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); - } - } - if ($documentSecurity) { $documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $allQueries); $total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT); From b2b714f71315bc877285f8f7b89182e5c33914b8 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 22 Aug 2022 13:48:35 +0000 Subject: [PATCH 06/17] feat: update naming convention and folder structure --- composer.lock | 16 +-- .../Validator/{Queries => }/Collection.php | 0 .../Queries/LimitOffsetCursorQuery.php | 64 ------------ .../Utopia/Database/Validator/Query.php | 21 ---- .../Database/Validator/Query/Cursor.php | 44 +++++++++ .../Filter.php} | 43 ++------ .../Utopia/Database/Validator/Query/Limit.php | 97 +++++++++++++++++++ .../LimitOffsetQuery.php => Query/Offset.php} | 42 +++----- .../Order.php} | 28 +++--- .../Validator/{Queries => }/Users.php | 0 10 files changed, 184 insertions(+), 171 deletions(-) rename src/Appwrite/Utopia/Database/Validator/{Queries => }/Collection.php (100%) delete mode 100644 src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorQuery.php delete mode 100644 src/Appwrite/Utopia/Database/Validator/Query.php create mode 100644 src/Appwrite/Utopia/Database/Validator/Query/Cursor.php rename src/Appwrite/Utopia/Database/Validator/{Queries/LimitOffsetCursorFilterQuery.php => Query/Filter.php} (65%) create mode 100644 src/Appwrite/Utopia/Database/Validator/Query/Limit.php rename src/Appwrite/Utopia/Database/Validator/{Queries/LimitOffsetQuery.php => Query/Offset.php} (56%) rename src/Appwrite/Utopia/Database/Validator/{Queries/LimitOffsetCursorFilterOrderQuery.php => Query/Order.php} (52%) rename src/Appwrite/Utopia/Database/Validator/{Queries => }/Users.php (100%) diff --git a/composer.lock b/composer.lock index eb5849095e..b9176fe53e 100644 --- a/composer.lock +++ b/composer.lock @@ -3526,23 +3526,23 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.15", + "version": "9.2.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" + "reference": "2593003befdcc10db5e213f9f28814f5aa8ac073" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", - "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2593003befdcc10db5e213f9f28814f5aa8ac073", + "reference": "2593003befdcc10db5e213f9f28814f5aa8ac073", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.13.0", + "nikic/php-parser": "^4.14", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -3591,7 +3591,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.16" }, "funding": [ { @@ -3599,7 +3599,7 @@ "type": "github" } ], - "time": "2022-03-07T09:28:20+00:00" + "time": "2022-08-20T05:26:47+00:00" }, { "name": "phpunit/php-file-iterator", @@ -5383,5 +5383,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Collection.php b/src/Appwrite/Utopia/Database/Validator/Collection.php similarity index 100% rename from src/Appwrite/Utopia/Database/Validator/Queries/Collection.php rename to src/Appwrite/Utopia/Database/Validator/Collection.php diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorQuery.php b/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorQuery.php deleted file mode 100644 index f74d41474c..0000000000 --- a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorQuery.php +++ /dev/null @@ -1,64 +0,0 @@ -isValid($cursor)) { - return true; - } - - $this->message = 'Invalid cursor: ' . $validator->getDescription(); - return false; - } - - /** - * Is valid. - * - * Returns true if: - * 1. method is limit or offset and values are within range - * 2. method is cursorBefore or cursorAfter and value is not null - * - * Otherwise, returns false - * - * @param Query $value - * - * @return bool - */ - public function isValid($query): bool - { - // Validate method - $method = $query->getMethod(); - - if ($method === Query::TYPE_CURSORAFTER || $method === Query::TYPE_CURSORBEFORE) { - $cursor = $query->getValue(); - return $this->isValidCursor($cursor); - } - - return parent::isValid($query); - } -} diff --git a/src/Appwrite/Utopia/Database/Validator/Query.php b/src/Appwrite/Utopia/Database/Validator/Query.php deleted file mode 100644 index eeb5b51a76..0000000000 --- a/src/Appwrite/Utopia/Database/Validator/Query.php +++ /dev/null @@ -1,21 +0,0 @@ -isValid($cursor)) { - return true; - } - - $this->message = 'Invalid cursor: ' . $validator->getDescription(); - return false; - } -} diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Cursor.php b/src/Appwrite/Utopia/Database/Validator/Query/Cursor.php new file mode 100644 index 0000000000..0fb77ee0b6 --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Query/Cursor.php @@ -0,0 +1,44 @@ +getMethod(); + + if ($method === Query::TYPE_CURSORAFTER || $method === Query::TYPE_CURSORBEFORE) { + $cursor = $query->getValue(); + $validator = new UID(); + if ($validator->isValid($cursor)) { + return true; + } + $this->message = 'Invalid cursor: ' . $validator->getDescription(); + return false; + } + + return false; + } +} diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterQuery.php b/src/Appwrite/Utopia/Database/Validator/Query/Filter.php similarity index 65% rename from src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterQuery.php rename to src/Appwrite/Utopia/Database/Validator/Query/Filter.php index 71da608709..e32b3aab66 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterQuery.php +++ b/src/Appwrite/Utopia/Database/Validator/Query/Filter.php @@ -1,12 +1,12 @@ schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); } $this->maxValuesCount = $maxValuesCount; - - parent::__construct($maxLimit, $maxOffset); } protected function isValidAttribute($attribute): bool @@ -78,30 +74,10 @@ class LimitOffsetCursorFilterQuery extends LimitOffsetCursorQuery return true; } - protected function isValidContains(string $attribute, array $values): bool - { - if (!$this->isValidAttributeAndValues($attribute, $values)) { - return false; - } - - $attributeSchema = $this->schema[$attribute]; - - // Contains method only supports array attributes - if (!$attributeSchema['array']) { - $this->message = 'Query method only supported on array attributes: ' . Query::TYPE_CONTAINS; - return false; - } - - return true; - } - /** * Is valid. * - * Returns true if: - * 1. method is limit or offset and values are within range - * 2. method is cursorBefore or cursorAfter and value is not null - * 3. method is a filter method, attribute exists, and value matches attribute type + * Returns true if method is a filter method, attribute exists, and value matches attribute type * * Otherwise, returns false * @@ -116,9 +92,10 @@ class LimitOffsetCursorFilterQuery extends LimitOffsetCursorQuery $attribute = $query->getAttribute(); switch ($method) { - case Query::TYPE_CONTAINS: - $values = $query->getValues(); - return $this->isValidContains($attribute, $values); + // Do we support contains ? + // case Query::TYPE_CONTAINS: + // $values = $query->getValues(); + // return $this->isValidContains($attribute, $values); case Query::TYPE_EQUAL: case Query::TYPE_NOTEQUAL: @@ -131,7 +108,7 @@ class LimitOffsetCursorFilterQuery extends LimitOffsetCursorQuery return $this->isValidAttributeAndValues($attribute, $values); default: - return parent::isValid($query); + return false; } } } diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Limit.php b/src/Appwrite/Utopia/Database/Validator/Query/Limit.php new file mode 100644 index 0000000000..63f681f7d9 --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Query/Limit.php @@ -0,0 +1,97 @@ +maxLimit = $maxLimit; + } + + /** + * Get Description. + * + * Returns validator description + * + * @return string + */ + public function getDescription(): string + { + return $this->message; + } + + protected function isValidLimit($limit): bool + { + $validator = new Range(0, $this->maxLimit); + if ($validator->isValid($limit)) { + return true; + } + + $this->message = 'Invalid limit: ' . $validator->getDescription(); + return false; + } + + /** + * Is valid. + * + * Returns true if method is limit values are within range. + * + * @param Query $value + * + * @return bool + */ + public function isValid($query): bool + { + // Validate method + $method = $query->getMethod(); + + if ($method !== Query::LIMIT) { + $this->message = 'Query method invalid: ' . $method; + return false; + } + + $limit = $query->getValue(); + return $this->isValidLimit($limit); + } + + /** + * Is array + * + * Function will return true if object is array. + * + * @return bool + */ + public function isArray(): bool + { + return false; + } + + /** + * Get Type + * + * Returns validator type. + * + * @return string + */ + public function getType(): string + { + return self::TYPE_OBJECT; + } +} diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetQuery.php b/src/Appwrite/Utopia/Database/Validator/Query/Offset.php similarity index 56% rename from src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetQuery.php rename to src/Appwrite/Utopia/Database/Validator/Query/Offset.php index e11590757e..a319c91502 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetQuery.php +++ b/src/Appwrite/Utopia/Database/Validator/Query/Offset.php @@ -1,31 +1,27 @@ maxLimit = $maxLimit; $this->maxOffset = $maxOffset; } @@ -41,17 +37,6 @@ class LimitOffsetQuery extends Validator return $this->message; } - protected function isValidLimit($limit): bool - { - $validator = new Range(0, $this->maxLimit); - if ($validator->isValid($limit)) { - return true; - } - - $this->message = 'Invalid limit: ' . $validator->getDescription(); - return false; - } - protected function isValidOffset($offset): bool { $validator = new Range(0, $this->maxOffset); @@ -66,7 +51,7 @@ class LimitOffsetQuery extends Validator /** * Is valid. * - * Returns true if method is limit or offset and values are within range. + * Returns true if method is offset and values are within range. * * @param Query $value * @@ -76,19 +61,14 @@ class LimitOffsetQuery extends Validator { // Validate method $method = $query->getMethod(); - switch ($method) { - case Query::TYPE_LIMIT: - $limit = $query->getValue(); - return $this->isValidLimit($limit); - - case Query::TYPE_OFFSET: - $offset = $query->getValue(); - return $this->isValidOffset($offset); - - default: - $this->message = 'Query method invalid: ' . $method; - return false; + + if ($method !== Query::TYPE_OFFSET) { + $this->message = 'Query method invalid: ' . $method; + return false; } + + $offset = $query->getValue(); + return $this->isValidOffset($offset); } /** diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterOrderQuery.php b/src/Appwrite/Utopia/Database/Validator/Query/Order.php similarity index 52% rename from src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterOrderQuery.php rename to src/Appwrite/Utopia/Database/Validator/Query/Order.php index 3a8459fa47..9ee40fa117 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterOrderQuery.php +++ b/src/Appwrite/Utopia/Database/Validator/Query/Order.php @@ -1,11 +1,11 @@ schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); } + } - $this->maxValuesCount = $maxValuesCount; + protected function isValidAttribute($attribute): bool + { + // Search for attribute in schema + if (!isset($this->schema[$attribute])) { + $this->message = 'Attribute not found in schema: ' . $attribute; + return false; + } - parent::__construct($maxLimit, $maxOffset); + return true; } /** * Is valid. * - * Returns true if: - * 1. method is limit or offset and values are within range - * 2. method is cursorBefore or cursorAfter and value is not null - * 3. method is a filter method, attribute exists, and value matches attribute type - * 4. method is orderAsc or orderDesc and attribute exists or is empty string + * Returns true if method is ORDER_ASC or ORDER_DESC and attributes are valid * * Otherwise, returns false * diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Users.php b/src/Appwrite/Utopia/Database/Validator/Users.php similarity index 100% rename from src/Appwrite/Utopia/Database/Validator/Queries/Users.php rename to src/Appwrite/Utopia/Database/Validator/Users.php From 228bedd6d01d70c6ac0c321600f9ac5a60bcd5db Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 22 Aug 2022 14:05:49 +0000 Subject: [PATCH 07/17] feat: update file structure --- src/Appwrite/Utopia/Database/Validator/Queries.php | 2 -- .../Utopia/Database/Validator/{ => Queries}/Collection.php | 0 src/Appwrite/Utopia/Database/Validator/{ => Queries}/Users.php | 0 3 files changed, 2 deletions(-) rename src/Appwrite/Utopia/Database/Validator/{ => Queries}/Collection.php (100%) rename src/Appwrite/Utopia/Database/Validator/{ => Queries}/Users.php (100%) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries.php b/src/Appwrite/Utopia/Database/Validator/Queries.php index cc675429e4..83248b4ebf 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries.php @@ -23,8 +23,6 @@ class Queries extends Validator * Queries constructor * * @param Validator $validator used to validate each query - * @param Document[] $attributes allowed attributes to be queried - * @param Document[] $indexes available for strict query matching * @param bool $strict */ public function __construct(Validator $validator) diff --git a/src/Appwrite/Utopia/Database/Validator/Collection.php b/src/Appwrite/Utopia/Database/Validator/Queries/Collection.php similarity index 100% rename from src/Appwrite/Utopia/Database/Validator/Collection.php rename to src/Appwrite/Utopia/Database/Validator/Queries/Collection.php diff --git a/src/Appwrite/Utopia/Database/Validator/Users.php b/src/Appwrite/Utopia/Database/Validator/Queries/Users.php similarity index 100% rename from src/Appwrite/Utopia/Database/Validator/Users.php rename to src/Appwrite/Utopia/Database/Validator/Queries/Users.php From afa71bcaf3eafcdf70358c5316f936ed69f29691 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 22 Aug 2022 14:09:04 +0000 Subject: [PATCH 08/17] feat: update file structure --- .../Database/Validator/{Queries => Queries-123}/Collection.php | 0 .../Utopia/Database/Validator/{Queries => Queries-123}/Users.php | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/Appwrite/Utopia/Database/Validator/{Queries => Queries-123}/Collection.php (100%) rename src/Appwrite/Utopia/Database/Validator/{Queries => Queries-123}/Users.php (100%) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Collection.php b/src/Appwrite/Utopia/Database/Validator/Queries-123/Collection.php similarity index 100% rename from src/Appwrite/Utopia/Database/Validator/Queries/Collection.php rename to src/Appwrite/Utopia/Database/Validator/Queries-123/Collection.php diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Users.php b/src/Appwrite/Utopia/Database/Validator/Queries-123/Users.php similarity index 100% rename from src/Appwrite/Utopia/Database/Validator/Queries/Users.php rename to src/Appwrite/Utopia/Database/Validator/Queries-123/Users.php From 3fe22b7ed02c1696f8b8e13435218c0135e217de Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 22 Aug 2022 14:10:10 +0000 Subject: [PATCH 09/17] feat: update file structure --- .../Database/Validator/{Queries-123 => Queries}/Collection.php | 0 .../Utopia/Database/Validator/{Queries-123 => Queries}/Users.php | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/Appwrite/Utopia/Database/Validator/{Queries-123 => Queries}/Collection.php (100%) rename src/Appwrite/Utopia/Database/Validator/{Queries-123 => Queries}/Users.php (100%) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries-123/Collection.php b/src/Appwrite/Utopia/Database/Validator/Queries/Collection.php similarity index 100% rename from src/Appwrite/Utopia/Database/Validator/Queries-123/Collection.php rename to src/Appwrite/Utopia/Database/Validator/Queries/Collection.php diff --git a/src/Appwrite/Utopia/Database/Validator/Queries-123/Users.php b/src/Appwrite/Utopia/Database/Validator/Queries/Users.php similarity index 100% rename from src/Appwrite/Utopia/Database/Validator/Queries-123/Users.php rename to src/Appwrite/Utopia/Database/Validator/Queries/Users.php From 0304a579159514d82fa69b3bb35c080a49d87d06 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 22 Aug 2022 14:12:52 +0000 Subject: [PATCH 10/17] feat: remove queries --- .../Database/Validator/Queries/Collection.php | 58 ------------------- .../Database/Validator/Queries/Users.php | 31 ---------- 2 files changed, 89 deletions(-) delete mode 100644 src/Appwrite/Utopia/Database/Validator/Queries/Collection.php delete mode 100644 src/Appwrite/Utopia/Database/Validator/Queries/Users.php diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Collection.php b/src/Appwrite/Utopia/Database/Validator/Queries/Collection.php deleted file mode 100644 index 0404f299af..0000000000 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Collection.php +++ /dev/null @@ -1,58 +0,0 @@ - $key, - 'type' => $attribute['type'], - 'array' => $attribute['array'], - ]); - } - - $indexes = []; - foreach ($allowedAttributes as $attribute) { - $indexes[] = new Document([ - 'status' => 'available', - 'type' => Database::INDEX_KEY, - 'attributes' => [$attribute] - ]); - } - $indexes[] = new Document([ - 'status' => 'available', - 'type' => Database::INDEX_FULLTEXT, - 'attributes' => ['search'] - ]); - - parent::__construct(new QueryValidator($attributes), $attributes, $indexes, true); - } -} diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Users.php b/src/Appwrite/Utopia/Database/Validator/Queries/Users.php deleted file mode 100644 index 59373c2cab..0000000000 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Users.php +++ /dev/null @@ -1,31 +0,0 @@ - Date: Mon, 22 Aug 2022 14:14:40 +0000 Subject: [PATCH 11/17] feat: remove queries --- .../Database/Validator/Queries/Collection.php | 58 +++++++++++++++++++ .../Database/Validator/Queries/Users.php | 31 ++++++++++ 2 files changed, 89 insertions(+) create mode 100644 src/Appwrite/Utopia/Database/Validator/Queries/Collection.php create mode 100644 src/Appwrite/Utopia/Database/Validator/Queries/Users.php diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Collection.php b/src/Appwrite/Utopia/Database/Validator/Queries/Collection.php new file mode 100644 index 0000000000..dffaabee6d --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Collection.php @@ -0,0 +1,58 @@ + $key, + 'type' => $attribute['type'], + 'array' => $attribute['array'], + ]); + } + + $indexes = []; + foreach ($allowedAttributes as $attribute) { + $indexes[] = new Document([ + 'status' => 'available', + 'type' => Database::INDEX_KEY, + 'attributes' => [$attribute] + ]); + } + $indexes[] = new Document([ + 'status' => 'available', + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['search'] + ]); + + parent::__construct(new QueryValidator($attributes), $attributes, $indexes, true); + } +} \ No newline at end of file diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Users.php b/src/Appwrite/Utopia/Database/Validator/Queries/Users.php new file mode 100644 index 0000000000..b61ec9c086 --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Users.php @@ -0,0 +1,31 @@ + Date: Mon, 22 Aug 2022 14:37:35 +0000 Subject: [PATCH 12/17] feat: update queries constructor --- src/Appwrite/Utopia/Database/Validator/Queries.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries.php b/src/Appwrite/Utopia/Database/Validator/Queries.php index 83248b4ebf..8bcf60fe1b 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries.php @@ -25,9 +25,9 @@ class Queries extends Validator * @param Validator $validator used to validate each query * @param bool $strict */ - public function __construct(Validator $validator) + public function __construct(...$validators) { - $this->validator = $validator; + $this->validators = $validators; } /** From 0fd55cb13caeeccb86c3519b368c6842f31d9892 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 22 Aug 2022 14:44:28 +0000 Subject: [PATCH 13/17] feat: update queries constructor --- src/Appwrite/Utopia/Database/Validator/Queries.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries.php b/src/Appwrite/Utopia/Database/Validator/Queries.php index 8bcf60fe1b..d518ae64e3 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries.php @@ -22,10 +22,9 @@ class Queries extends Validator /** * Queries constructor * - * @param Validator $validator used to validate each query - * @param bool $strict + * @param $validators - a list of validators */ - public function __construct(...$validators) + public function __construct(Validator ...$validators) { $this->validators = $validators; } @@ -66,9 +65,11 @@ class Queries extends Validator } } - if (!$this->validator->isValid($query)) { - $this->message = 'Query not valid: ' . $this->validator->getDescription(); - return false; + foreach ($this->validators as $validator) { + if (!$validator->isValid($query)) { + $this->message = 'Query not valid: ' . $this->validator->getDescription(); + return false; + } } $queries[] = $query; From cff0fd9476f24f67c811a85af3cd813752bfdfbc Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 22 Aug 2022 15:04:55 +0000 Subject: [PATCH 14/17] feat: update queries constructor --- .../Utopia/Database/Validator/Queries.php | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries.php b/src/Appwrite/Utopia/Database/Validator/Queries.php index d518ae64e3..1ad5b2e74f 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries.php @@ -24,9 +24,11 @@ class Queries extends Validator * * @param $validators - a list of validators */ - public function __construct(Validator ...$validators) + public function __construct(Limit $limit = null, Offset $offset = null, Order $order) { - $this->validators = $validators; + $this->limit = $limit; + $this->offset = $offset; + $this->order = $order; } /** @@ -65,11 +67,21 @@ class Queries extends Validator } } - foreach ($this->validators as $validator) { - if (!$validator->isValid($query)) { - $this->message = 'Query not valid: ' . $this->validator->getDescription(); + $method = $query->getMethod(); + switch ($method) { + case Query::TYPE_LIMIT: + $validator = $this->limit; + case Query::TYPE_OFFSET: + $validator = $this->offset; + case Query::TYPE_ORDER: + $validator = $this->order; + default: return false; - } + } + + if (!$validator->isValid($query)) { + $this->message = 'Query not valid: ' . $this->validator->getDescription(); + return false; } $queries[] = $query; From 464738274d0aa4a56a270395e587511cd9b5e05f Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 22 Aug 2022 15:06:11 +0000 Subject: [PATCH 15/17] feat: update queries constructor --- src/Appwrite/Utopia/Database/Validator/Queries.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries.php b/src/Appwrite/Utopia/Database/Validator/Queries.php index 1ad5b2e74f..0c3d144eb9 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries.php @@ -79,7 +79,7 @@ class Queries extends Validator return false; } - if (!$validator->isValid($query)) { + if ($validator && !$validator->isValid($query)) { $this->message = 'Query not valid: ' . $this->validator->getDescription(); return false; } From e09cb3a1cb5550c7e8c646a94e4497deb465a997 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 22 Aug 2022 15:11:47 +0000 Subject: [PATCH 16/17] feat: update queries constructor --- src/Appwrite/Utopia/Database/Validator/Queries.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries.php b/src/Appwrite/Utopia/Database/Validator/Queries.php index 0c3d144eb9..826f783c4d 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries.php @@ -24,11 +24,13 @@ class Queries extends Validator * * @param $validators - a list of validators */ - public function __construct(Limit $limit = null, Offset $offset = null, Order $order) + public function __construct(Limit $limit = null, Offset $offset = null, Order $order = null, Cursor $cursor = null, Filter $filter = null) { $this->limit = $limit; $this->offset = $offset; $this->order = $order; + $this->filter = $filter; + $this->cursor = $cursor; } /** From 66ff297103a3bff1cb2cce327824d06b022672b6 Mon Sep 17 00:00:00 2001 From: Steven Date: Mon, 22 Aug 2022 19:12:30 +0000 Subject: [PATCH 17/17] Refactor Queries Validation --- app/controllers/api/databases.php | 16 ++- app/controllers/api/users.php | 5 +- .../Database/Validator/IndexedQueries.php | 14 +- .../Utopia/Database/Validator/Queries.php | 69 ++++++---- .../Database/Validator/Queries/Collection.php | 38 +++++- .../Database/Validator/Queries/Users.php | 6 +- .../Utopia/Database/Validator/Query/Base.php | 70 ++++++++++ .../Database/Validator/Query/Cursor.php | 14 +- .../Database/Validator/Query/Filter.php | 14 +- .../Utopia/Database/Validator/Query/Limit.php | 46 +------ .../Database/Validator/Query/Offset.php | 46 +------ .../Utopia/Database/Validator/Query/Order.php | 15 ++- .../Database/Validator/IndexedQueriesTest.php | 121 ++++++++++++++++++ .../Validator/Queries/CollectionTest.php | 43 +++++++ .../LimitOffsetCursorFilterOrderQueryTest.php | 94 -------------- .../LimitOffsetCursorFilterQueryTest.php | 88 ------------- .../Queries/LimitOffsetCursorQueryTest.php | 74 ----------- .../Queries/LimitOffsetQueryTest.php | 74 ----------- .../Database/Validator/Queries/UsersTest.php | 40 ++++++ .../Utopia/Database/Validator/QueriesTest.php | 76 +++++++++++ .../Database/Validator/Query/CursorTest.php | 41 ++++++ .../Database/Validator/Query/FilterTest.php | 59 +++++++++ .../Database/Validator/Query/LimitTest.php | 37 ++++++ .../Database/Validator/Query/OffsetTest.php | 41 ++++++ .../Database/Validator/Query/OrderTest.php | 54 ++++++++ 25 files changed, 716 insertions(+), 479 deletions(-) create mode 100644 src/Appwrite/Utopia/Database/Validator/Query/Base.php create mode 100644 tests/unit/Utopia/Database/Validator/IndexedQueriesTest.php create mode 100644 tests/unit/Utopia/Database/Validator/Queries/CollectionTest.php delete mode 100644 tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterOrderQueryTest.php delete mode 100644 tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterQueryTest.php delete mode 100644 tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorQueryTest.php delete mode 100644 tests/unit/Utopia/Database/Validator/Queries/LimitOffsetQueryTest.php create mode 100644 tests/unit/Utopia/Database/Validator/Queries/UsersTest.php create mode 100644 tests/unit/Utopia/Database/Validator/QueriesTest.php create mode 100644 tests/unit/Utopia/Database/Validator/Query/CursorTest.php create mode 100644 tests/unit/Utopia/Database/Validator/Query/FilterTest.php create mode 100644 tests/unit/Utopia/Database/Validator/Query/LimitTest.php create mode 100644 tests/unit/Utopia/Database/Validator/Query/OffsetTest.php create mode 100644 tests/unit/Utopia/Database/Validator/Query/OrderTest.php diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 7efd9fac8e..372c8eea95 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -38,7 +38,11 @@ use Appwrite\Network\Validator\IP; use Appwrite\Network\Validator\URL; use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\Utopia\Database\Validator\IndexedQueries; -use Appwrite\Utopia\Database\Validator\Queries\LimitOffsetCursorFilterOrderQuery; +use Appwrite\Utopia\Database\Validator\Query\Cursor as CursorQueryValidator; +use Appwrite\Utopia\Database\Validator\Query\Filter as FilterQueryValidator; +use Appwrite\Utopia\Database\Validator\Query\Limit as LimitQueryValidator; +use Appwrite\Utopia\Database\Validator\Query\Offset as OffsetQueryValidator; +use Appwrite\Utopia\Database\Validator\Query\Order as OrderQueryValidator; use Appwrite\Utopia\Response; use Appwrite\Detector\Detector; use Appwrite\Event\Database as EventDatabase; @@ -2048,7 +2052,15 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') ]), ] ); - $validator = new IndexedQueries(new LimitOffsetCursorFilterOrderQuery(attributes: $attributes), $attributes, $collection->getAttribute('indexes', [])); + $validator = new IndexedQueries( + $attributes, + $collection->getAttribute('indexes', []), + new CursorQueryValidator(), + new FilterQueryValidator($attributes), + new LimitQueryValidator(), + new OffsetQueryValidator(), + new OrderQueryValidator(), + ); if (!$validator->isValid($queries)) { throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); } diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 87a8959f91..3856faeee0 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -11,7 +11,8 @@ use Appwrite\Stats\Stats; use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\Utopia\Database\Validator\Queries; use Appwrite\Utopia\Database\Validator\Queries\Users; -use Appwrite\Utopia\Database\Validator\Queries\LimitOffsetQuery; +use Appwrite\Utopia\Database\Validator\Query\Limit; +use Appwrite\Utopia\Database\Validator\Query\Offset; use Appwrite\Utopia\Response; use Utopia\App; use Utopia\Audit\Audit; @@ -574,7 +575,7 @@ App::get('/v1/users/:userId/logs') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->param('userId', '', new UID(), 'User ID.') - ->param('queries', [], new Queries(new LimitOffsetQuery()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true) + ->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') ->inject('locale') diff --git a/src/Appwrite/Utopia/Database/Validator/IndexedQueries.php b/src/Appwrite/Utopia/Database/Validator/IndexedQueries.php index f1f3e62eb9..1cc0429018 100644 --- a/src/Appwrite/Utopia/Database/Validator/IndexedQueries.php +++ b/src/Appwrite/Utopia/Database/Validator/IndexedQueries.php @@ -2,11 +2,10 @@ namespace Appwrite\Utopia\Database\Validator; -use Appwrite\Utopia\Database\Validator\Queries; +use Appwrite\Utopia\Database\Validator\Query\Base; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; -use Utopia\Database\Validator\Query as QueryValidator; class IndexedQueries extends Queries { @@ -25,18 +24,13 @@ class IndexedQueries extends Queries * * This Queries Validator filters indexes for only available indexes * - * @param QueryValidator $validator * @param Document[] $attributes * @param Document[] $indexes + * @param Base ...$validators * @param bool $strict */ - public function __construct($validator, $attributes = [], $indexes = []) + public function __construct($attributes = [], $indexes = [], Base ...$validators) { - // Remove failed/stuck/processing indexes - $availableIndexes = \array_filter($indexes, function ($index) { - return $index->getAttribute('status') === 'available'; - }); - $this->attributes = $attributes; $this->indexes[] = new Document([ @@ -58,7 +52,7 @@ class IndexedQueries extends Queries $this->indexes[] = $index; } - parent::__construct($validator); + parent::__construct(...$validators); } /** diff --git a/src/Appwrite/Utopia/Database/Validator/Queries.php b/src/Appwrite/Utopia/Database/Validator/Queries.php index 826f783c4d..ec77971496 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries.php @@ -2,9 +2,8 @@ namespace Appwrite\Utopia\Database\Validator; +use Appwrite\Utopia\Database\Validator\Query\Base; use Utopia\Validator; -use Utopia\Database\Document; -use Utopia\Database\Validator\Query as QueryValidator; use Utopia\Database\Query; class Queries extends Validator @@ -15,22 +14,18 @@ class Queries extends Validator protected $message = 'Invalid queries'; /** - * @var QueryValidator + * @var Base[] */ - protected $validator; + protected $validators; /** * Queries constructor * - * @param $validators - a list of validators + * @param Base ...$validators a list of validators */ - public function __construct(Limit $limit = null, Offset $offset = null, Order $order = null, Cursor $cursor = null, Filter $filter = null) + public function __construct(Base ...$validators) { - $this->limit = $limit; - $this->offset = $offset; - $this->order = $order; - $this->filter = $filter; - $this->cursor = $cursor; + $this->validators = $validators; } /** @@ -58,7 +53,6 @@ class Queries extends Validator */ public function isValid($value): bool { - $queries = []; foreach ($value as $query) { if (!$query instanceof Query) { try { @@ -70,23 +64,52 @@ class Queries extends Validator } $method = $query->getMethod(); + $methodType = ''; switch ($method) { case Query::TYPE_LIMIT: - $validator = $this->limit; + $methodType = Base::METHOD_TYPE_LIMIT; + break; case Query::TYPE_OFFSET: - $validator = $this->offset; - case Query::TYPE_ORDER: - $validator = $this->order; + $methodType = Base::METHOD_TYPE_OFFSET; + break; + case Query::TYPE_CURSORAFTER: + case Query::TYPE_CURSORBEFORE: + $methodType = Base::METHOD_TYPE_CURSOR; + break; + case Query::TYPE_ORDERASC: + case Query::TYPE_ORDERDESC: + $methodType = Base::METHOD_TYPE_ORDER; + break; + case Query::TYPE_EQUAL: + case Query::TYPE_NOTEQUAL: + case Query::TYPE_LESSER: + case Query::TYPE_LESSEREQUAL: + case Query::TYPE_GREATER: + case Query::TYPE_GREATEREQUAL: + case Query::TYPE_SEARCH: + $methodType = Base::METHOD_TYPE_FILTER; + break; default: - return false; - } - - if ($validator && !$validator->isValid($query)) { - $this->message = 'Query not valid: ' . $this->validator->getDescription(); - return false; + break; } - $queries[] = $query; + $methodIsValid = false; + foreach ($this->validators as $validator) { + if ($validator->getMethodType() !== $methodType) { + continue; + } + if (!$validator->isValid($query)) { + $this->message = 'Query not valid: ' . $validator->getDescription(); + return false; + } + + $methodIsValid = true; + } + + if (!$methodIsValid) { + $this->message = 'Query method not valid: ' . $method; + return false; + } } return true; diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Collection.php b/src/Appwrite/Utopia/Database/Validator/Queries/Collection.php index dffaabee6d..0c29ecd2ff 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Collection.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Collection.php @@ -2,13 +2,17 @@ namespace Appwrite\Utopia\Database\Validator\Queries; +use Appwrite\Utopia\Database\Validator\IndexedQueries; +use Appwrite\Utopia\Database\Validator\Query\Limit; +use Appwrite\Utopia\Database\Validator\Query\Offset; +use Appwrite\Utopia\Database\Validator\Query\Cursor; +use Appwrite\Utopia\Database\Validator\Query\Filter; +use Appwrite\Utopia\Database\Validator\Query\Order; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; -use Utopia\Database\Validator\Queries as QueriesValidator; -use Utopia\Database\Validator\Query as QueryValidator; -class Collection extends QueriesValidator +class Collection extends IndexedQueries { /** * Expression constructor @@ -39,6 +43,22 @@ class Collection extends QueriesValidator ]); } + $attributes[] = new Document([ + 'key' => '$id', + 'type' => Database::VAR_STRING, + 'array' => false, + ]); + $attributes[] = new Document([ + '$id' => '$createdAt', + 'type' => Database::VAR_DATETIME, + 'array' => false, + ]); + $attributes[] = new Document([ + '$id' => '$updatedAt', + 'type' => Database::VAR_DATETIME, + 'array' => false, + ]); + $indexes = []; foreach ($allowedAttributes as $attribute) { $indexes[] = new Document([ @@ -53,6 +73,14 @@ class Collection extends QueriesValidator 'attributes' => ['search'] ]); - parent::__construct(new QueryValidator($attributes), $attributes, $indexes, true); + $validators = [ + new Limit(), + new Offset(), + new Cursor(), + new Filter($attributes), + new Order($attributes), + ]; + + parent::__construct($attributes, $indexes, ...$validators); } -} \ No newline at end of file +} diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Users.php b/src/Appwrite/Utopia/Database/Validator/Queries/Users.php index b61ec9c086..ffe30f1209 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Users.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Users.php @@ -7,9 +7,6 @@ use Appwrite\Utopia\Database\Validator\Queries\Collection; class Users extends Collection { public const ALLOWED_ATTRIBUTES = [ - '$id', - '$createdAt', - '$updatedAt', 'name', 'email', 'phone', @@ -18,6 +15,7 @@ class Users extends Collection 'registration', 'emailVerification', 'phoneVerification', + 'search', ]; /** @@ -28,4 +26,4 @@ class Users extends Collection { parent::__construct('users', self::ALLOWED_ATTRIBUTES); } -} \ No newline at end of file +} diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Base.php b/src/Appwrite/Utopia/Database/Validator/Query/Base.php new file mode 100644 index 0000000000..b23d594ea7 --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Query/Base.php @@ -0,0 +1,70 @@ +message; + } + + /** + * Is array + * + * Function will return true if object is array. + * + * @return bool + */ + public function isArray(): bool + { + return false; + } + + /** + * Get Type + * + * Returns validator type. + * + * @return string + */ + public function getType(): string + { + return self::TYPE_OBJECT; + } + + /** + * Is valid. + * + * @param Query $value + * + * @return bool + */ + abstract public function isValid($query): bool; + + /** + * Returns what type of query this Validator is for + */ + abstract public function getMethodType(): string; +} diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Cursor.php b/src/Appwrite/Utopia/Database/Validator/Query/Cursor.php index 0fb77ee0b6..42bff08a1d 100644 --- a/src/Appwrite/Utopia/Database/Validator/Query/Cursor.php +++ b/src/Appwrite/Utopia/Database/Validator/Query/Cursor.php @@ -2,17 +2,12 @@ namespace Appwrite\Utopia\Database\Validator\Query; -use Utopia\Validator; +use Appwrite\Utopia\Database\Validator\Query\Base; use Utopia\Database\Query; use Utopia\Database\Validator\UID; -class Cursor extends Validator +class Cursor extends Base { - /** - * @var string - */ - protected $message = 'Invalid query'; - /** * Is valid. * @@ -41,4 +36,9 @@ class Cursor extends Validator return false; } + + public function getMethodType(): string + { + return self::METHOD_TYPE_CURSOR; + } } diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Filter.php b/src/Appwrite/Utopia/Database/Validator/Query/Filter.php index e32b3aab66..096d036907 100644 --- a/src/Appwrite/Utopia/Database/Validator/Query/Filter.php +++ b/src/Appwrite/Utopia/Database/Validator/Query/Filter.php @@ -2,11 +2,11 @@ namespace Appwrite\Utopia\Database\Validator\Query; -use Utopia\Validator; +use Appwrite\Utopia\Database\Validator\Query\Base; use Utopia\Database\Database; use Utopia\Database\Query; -class Filter extends Validator +class Filter extends Base { /** * @var string @@ -92,11 +92,6 @@ class Filter extends Validator $attribute = $query->getAttribute(); switch ($method) { - // Do we support contains ? - // case Query::TYPE_CONTAINS: - // $values = $query->getValues(); - // return $this->isValidContains($attribute, $values); - case Query::TYPE_EQUAL: case Query::TYPE_NOTEQUAL: case Query::TYPE_LESSER: @@ -111,4 +106,9 @@ class Filter extends Validator return false; } } + + public function getMethodType(): string + { + return self::METHOD_TYPE_FILTER; + } } diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Limit.php b/src/Appwrite/Utopia/Database/Validator/Query/Limit.php index 63f681f7d9..232df93666 100644 --- a/src/Appwrite/Utopia/Database/Validator/Query/Limit.php +++ b/src/Appwrite/Utopia/Database/Validator/Query/Limit.php @@ -2,17 +2,12 @@ namespace Appwrite\Utopia\Database\Validator\Query; +use Appwrite\Utopia\Database\Validator\Query\Base; use Utopia\Database\Query; use Utopia\Validator\Range; -use Utopia\Validator; -class Limit extends Validator +class Limit extends Base { - /** - * @var string - */ - protected $message = 'Invalid query'; - protected int $maxLimit; /** @@ -25,18 +20,6 @@ class Limit extends Validator $this->maxLimit = $maxLimit; } - /** - * Get Description. - * - * Returns validator description - * - * @return string - */ - public function getDescription(): string - { - return $this->message; - } - protected function isValidLimit($limit): bool { $validator = new Range(0, $this->maxLimit); @@ -62,7 +45,7 @@ class Limit extends Validator // Validate method $method = $query->getMethod(); - if ($method !== Query::LIMIT) { + if ($method !== Query::TYPE_LIMIT) { $this->message = 'Query method invalid: ' . $method; return false; } @@ -71,27 +54,8 @@ class Limit extends Validator return $this->isValidLimit($limit); } - /** - * Is array - * - * Function will return true if object is array. - * - * @return bool - */ - public function isArray(): bool + public function getMethodType(): string { - return false; - } - - /** - * Get Type - * - * Returns validator type. - * - * @return string - */ - public function getType(): string - { - return self::TYPE_OBJECT; + return self::METHOD_TYPE_LIMIT; } } diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Offset.php b/src/Appwrite/Utopia/Database/Validator/Query/Offset.php index a319c91502..9f832a7c62 100644 --- a/src/Appwrite/Utopia/Database/Validator/Query/Offset.php +++ b/src/Appwrite/Utopia/Database/Validator/Query/Offset.php @@ -2,17 +2,12 @@ namespace Appwrite\Utopia\Database\Validator\Query; +use Appwrite\Utopia\Database\Validator\Query\Base; use Utopia\Database\Query; use Utopia\Validator\Range; -use Utopia\Validator; -class Offset extends Validator +class Offset extends Base { - /** - * @var string - */ - protected $message = 'Invalid query'; - protected int $maxOffset; /** @@ -25,18 +20,6 @@ class Offset extends Validator $this->maxOffset = $maxOffset; } - /** - * Get Description. - * - * Returns validator description - * - * @return string - */ - public function getDescription(): string - { - return $this->message; - } - protected function isValidOffset($offset): bool { $validator = new Range(0, $this->maxOffset); @@ -61,7 +44,7 @@ class Offset extends Validator { // Validate method $method = $query->getMethod(); - + if ($method !== Query::TYPE_OFFSET) { $this->message = 'Query method invalid: ' . $method; return false; @@ -71,27 +54,8 @@ class Offset extends Validator return $this->isValidOffset($offset); } - /** - * Is array - * - * Function will return true if object is array. - * - * @return bool - */ - public function isArray(): bool + public function getMethodType(): string { - return false; - } - - /** - * Get Type - * - * Returns validator type. - * - * @return string - */ - public function getType(): string - { - return self::TYPE_OBJECT; + return self::METHOD_TYPE_OFFSET; } } diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Order.php b/src/Appwrite/Utopia/Database/Validator/Query/Order.php index 9ee40fa117..0c12d7ac44 100644 --- a/src/Appwrite/Utopia/Database/Validator/Query/Order.php +++ b/src/Appwrite/Utopia/Database/Validator/Query/Order.php @@ -2,16 +2,12 @@ namespace Appwrite\Utopia\Database\Validator\Query; +use Appwrite\Utopia\Database\Validator\Query\Base; use Utopia\Database\Query; use Utopia\Validator; -class Order extends Validator +class Order extends Base { - /** - * @var string - */ - protected $message = 'Invalid query'; - /** * @var array */ @@ -62,6 +58,11 @@ class Order extends Validator return $this->isValidAttribute($attribute); } - return parent::isValid($query); + return false; + } + + public function getMethodType(): string + { + return self::METHOD_TYPE_ORDER; } } diff --git a/tests/unit/Utopia/Database/Validator/IndexedQueriesTest.php b/tests/unit/Utopia/Database/Validator/IndexedQueriesTest.php new file mode 100644 index 0000000000..52375004ca --- /dev/null +++ b/tests/unit/Utopia/Database/Validator/IndexedQueriesTest.php @@ -0,0 +1,121 @@ +assertEquals(true, $validator->isValid([])); + } + + public function testInvalidQuery(): void + { + $validator = new IndexedQueries(); + + $this->assertEquals(false, $validator->isValid(["this.is.invalid"])); + } + + public function testInvalidMethod(): void + { + $validator = new IndexedQueries(); + $this->assertEquals(false, $validator->isValid(['equal("attr", "value")'])); + + $validator = new IndexedQueries([], [], new Limit()); + $this->assertEquals(false, $validator->isValid(['equal("attr", "value")'])); + } + + public function testInvalidValue(): void + { + $validator = new IndexedQueries([], [], new Limit()); + $this->assertEquals(false, $validator->isValid(['limit(-1)'])); + } + + public function testValid(): void + { + $attributes = [ + new Document([ + 'key' => 'name', + 'type' => Database::VAR_STRING, + 'array' => false, + ]), + ]; + $indexes = [ + new Document([ + 'status' => 'available', + 'type' => Database::INDEX_KEY, + 'attributes' => ['name'], + ]), + new Document([ + 'status' => 'available', + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['name'], + ]), + ]; + $validator = new IndexedQueries( + $attributes, + $indexes, + new Cursor(), + new Filter($attributes), + new Limit(), + new Offset(), + new Order($attributes), + ); + $this->assertEquals(true, $validator->isValid(['cursorAfter("asdf")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['equal("name", "value")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['limit(10)']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['offset(10)']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['orderAsc("name")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['search("name", "value")']), $validator->getDescription()); + } + + public function testMissingIndex(): void + { + $attributes = [ + new Document([ + 'key' => 'name', + 'type' => Database::VAR_STRING, + 'array' => false, + ]), + ]; + $indexes = [ + new Document([ + 'status' => 'available', + 'type' => Database::INDEX_KEY, + 'attributes' => ['name'], + ]), + ]; + $validator = new IndexedQueries( + $attributes, + $indexes, + new Cursor(), + new Filter($attributes), + new Limit(), + new Offset(), + new Order($attributes), + ); + $this->assertEquals(false, $validator->isValid(['equal("dne", "value")']), $validator->getDescription()); + $this->assertEquals(false, $validator->isValid(['orderAsc("dne")']), $validator->getDescription()); + $this->assertEquals(false, $validator->isValid(['search("name", "value")']), $validator->getDescription()); + } +} diff --git a/tests/unit/Utopia/Database/Validator/Queries/CollectionTest.php b/tests/unit/Utopia/Database/Validator/Queries/CollectionTest.php new file mode 100644 index 0000000000..198fc2895d --- /dev/null +++ b/tests/unit/Utopia/Database/Validator/Queries/CollectionTest.php @@ -0,0 +1,43 @@ +assertEquals($validator->isValid([]), true); + } + + public function testValid(): void + { + $validator = new Collection('users', ['name', 'search']); + $this->assertEquals(true, $validator->isValid(['cursorAfter("asdf")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['equal("name", "value")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['limit(10)']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['offset(10)']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['orderAsc("name")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['search("search", "value")']), $validator->getDescription()); + } + + public function testMissingIndex(): void + { + $validator = new Collection('users', ['name']); + $this->assertEquals(false, $validator->isValid(['equal("dne", "value")']), $validator->getDescription()); + $this->assertEquals(false, $validator->isValid(['orderAsc("dne")']), $validator->getDescription()); + $this->assertEquals(false, $validator->isValid(['search("search", "value")']), $validator->getDescription()); + } +} diff --git a/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterOrderQueryTest.php b/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterOrderQueryTest.php deleted file mode 100644 index c46911b30e..0000000000 --- a/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterOrderQueryTest.php +++ /dev/null @@ -1,94 +0,0 @@ -validator = new LimitOffsetCursorFilterOrderQuery( - attributes: [ - new Document([ - 'key' => 'attr', - 'type' => Database::VAR_STRING, - 'array' => false, - ]), - ], - ); - } - - public function tearDown(): void - { - } - - public function testValue(): void - { - // Test for Success - $this->assertEquals($this->validator->isValid(Query::limit(1)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(0)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(100)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(1)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(0)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(5000)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderAsc('')), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderDesc('')), true, $this->validator->getDescription()); - - // Test for Failure - $this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::equal('dne', ['v'])), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::equal('', ['v'])), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderDesc('dne')), false, $this->validator->getDescription()); - } - - public function testValues(): void - { - - $validator = new Queries($this->validator); - - // Test for Success - $this->assertEquals($validator->isValid(['limit(1)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['limit(0)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['limit(100)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(1)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(0)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(5000)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['limit(25)', 'offset(25)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['cursorAfter("asdf")']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['cursorBefore("asdf")']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['equal("attr", "v")']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['orderAsc("attr")']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['orderAsc("")']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['orderDesc("attr")']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['orderDesc("")']), true, $validator->getDescription()); - - // Test for Failure - $this->assertEquals($validator->isValid(['limit(-1)']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['limit(101)']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(-1)']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(5001)']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['equal("dne", "v")']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['equal("", "v")']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['orderDesc("dne")']), false, $validator->getDescription()); - } -} diff --git a/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterQueryTest.php b/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterQueryTest.php deleted file mode 100644 index 1ae43ae37f..0000000000 --- a/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorFilterQueryTest.php +++ /dev/null @@ -1,88 +0,0 @@ -validator = new LimitOffsetCursorFilterQuery( - attributes: [ - new Document([ - 'key' => 'attr', - 'type' => Database::VAR_STRING, - 'array' => false, - ]), - ], - ); - } - - public function tearDown(): void - { - } - - public function testValue(): void - { - // Test for Success - $this->assertEquals($this->validator->isValid(Query::limit(1)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(0)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(100)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(1)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(0)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(5000)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), true, $this->validator->getDescription()); - - // Test for Failure - $this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::equal('dne', ['v'])), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::equal('', ['v'])), false, $this->validator->getDescription()); - } - - public function testValues(): void - { - - $validator = new Queries($this->validator); - - // Test for Success - $this->assertEquals($validator->isValid(['limit(1)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['limit(0)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['limit(100)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(1)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(0)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(5000)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['limit(25)', 'offset(25)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['cursorAfter("asdf")']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['cursorBefore("asdf")']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['equal("attr", "v")']), true, $validator->getDescription()); - - // Test for Failure - $this->assertEquals($validator->isValid(['limit(-1)']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['limit(101)']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(-1)']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(5001)']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['orderAsc("attr")']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['orderDesc("attr")']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['equal("dne", "v")']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['equal("", "v")']), false, $validator->getDescription()); - } -} diff --git a/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorQueryTest.php b/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorQueryTest.php deleted file mode 100644 index 77e6a1e257..0000000000 --- a/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetCursorQueryTest.php +++ /dev/null @@ -1,74 +0,0 @@ -validator = new LimitOffsetCursorQuery(); - } - - public function tearDown(): void - { - } - - public function testValue(): void - { - // Test for Success - $this->assertEquals($this->validator->isValid(Query::limit(1)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(0)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(100)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(1)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(0)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(5000)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), true, $this->validator->getDescription()); - - // Test for Failure - $this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription()); - } - - public function testValues(): void - { - - $validator = new Queries($this->validator); - - // Test for Success - $this->assertEquals($validator->isValid(['limit(1)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['limit(0)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['limit(100)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(1)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(0)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(5000)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['limit(25)', 'offset(25)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['cursorAfter("asdf")']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['cursorBefore("asdf")']), true, $validator->getDescription()); - - // Test for Failure - $this->assertEquals($validator->isValid(['limit(-1)']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['limit(101)']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(-1)']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(5001)']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['equal("attr", "v")']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['orderAsc("attr")']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['orderDesc("attr")']), false, $validator->getDescription()); - } -} diff --git a/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetQueryTest.php b/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetQueryTest.php deleted file mode 100644 index 724a1e5939..0000000000 --- a/tests/unit/Utopia/Database/Validator/Queries/LimitOffsetQueryTest.php +++ /dev/null @@ -1,74 +0,0 @@ -validator = new LimitOffsetQuery(); - } - - public function tearDown(): void - { - } - - public function testValue(): void - { - // Test for Success - $this->assertEquals($this->validator->isValid(Query::limit(1)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(0)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(100)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(1)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(0)), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(5000)), true, $this->validator->getDescription()); - - // Test for Failure - $this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription()); - } - - public function testValues(): void - { - - $validator = new Queries($this->validator); - - // Test for Success - $this->assertEquals($validator->isValid(['limit(1)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['limit(0)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['limit(100)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(1)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(0)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(5000)']), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(['limit(25)', 'offset(25)']), true, $validator->getDescription()); - - // Test for Failure - $this->assertEquals($validator->isValid(['limit(-1)']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['limit(101)']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(-1)']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['offset(5001)']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['cursorAfter("asdf")']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['cursorBefore("asdf")']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['equal("attr", "v")']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['orderAsc("attr")']), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(['orderDesc("attr")']), false, $validator->getDescription()); - } -} diff --git a/tests/unit/Utopia/Database/Validator/Queries/UsersTest.php b/tests/unit/Utopia/Database/Validator/Queries/UsersTest.php new file mode 100644 index 0000000000..24a818c128 --- /dev/null +++ b/tests/unit/Utopia/Database/Validator/Queries/UsersTest.php @@ -0,0 +1,40 @@ +assertEquals(true, $validator->isValid([]), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['equal("name", "value")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['equal("email", "value")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['equal("phone", "value")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['greaterThan("passwordUpdate", "2020-10-15 06:38")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['greaterThan("registration", "2020-10-15 06:38")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['equal("emailVerification", true)']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['equal("phoneVerification", true)']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['search("search", "value")']), $validator->getDescription()); + + /** + * Test for Failure + */ + $this->assertEquals(false, $validator->isValid(['equal("password", "value")']), $validator->getDescription()); + } +} diff --git a/tests/unit/Utopia/Database/Validator/QueriesTest.php b/tests/unit/Utopia/Database/Validator/QueriesTest.php new file mode 100644 index 0000000000..55e04c2b84 --- /dev/null +++ b/tests/unit/Utopia/Database/Validator/QueriesTest.php @@ -0,0 +1,76 @@ +assertEquals(true, $validator->isValid([])); + } + + public function testInvalidQuery(): void + { + $validator = new Queries(); + + $this->assertEquals(false, $validator->isValid(["this.is.invalid"])); + } + + public function testInvalidMethod(): void + { + $validator = new Queries(); + $this->assertEquals(false, $validator->isValid(['equal("attr", "value")'])); + + $validator = new Queries(new Limit()); + $this->assertEquals(false, $validator->isValid(['equal("attr", "value")'])); + } + + public function testInvalidValue(): void + { + $validator = new Queries(new Limit()); + $this->assertEquals(false, $validator->isValid(['limit(-1)'])); + } + + public function testValid(): void + { + $attributes = [ + new Document([ + 'key' => 'name', + 'type' => Database::VAR_STRING, + 'array' => false, + ]) + ]; + $validator = new Queries( + new Cursor(), + new Filter($attributes), + new Limit(), + new Offset(), + new Order($attributes), + ); + $this->assertEquals(true, $validator->isValid(['cursorAfter("asdf")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['equal("name", "value")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['limit(10)']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['offset(10)']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid(['orderAsc("name")']), $validator->getDescription()); + } +} diff --git a/tests/unit/Utopia/Database/Validator/Query/CursorTest.php b/tests/unit/Utopia/Database/Validator/Query/CursorTest.php new file mode 100644 index 0000000000..0afc8baddd --- /dev/null +++ b/tests/unit/Utopia/Database/Validator/Query/CursorTest.php @@ -0,0 +1,41 @@ +validator = new Cursor(); + } + + public function tearDown(): void + { + } + + public function testValue(): void + { + // Test for Success + $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), true, $this->validator->getDescription()); + + // Test for Failure + $this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription()); + } +} diff --git a/tests/unit/Utopia/Database/Validator/Query/FilterTest.php b/tests/unit/Utopia/Database/Validator/Query/FilterTest.php new file mode 100644 index 0000000000..8f2f1d44ba --- /dev/null +++ b/tests/unit/Utopia/Database/Validator/Query/FilterTest.php @@ -0,0 +1,59 @@ +validator = new Filter( + attributes: [ + new Document([ + 'key' => 'attr', + 'type' => Database::VAR_STRING, + 'array' => false, + ]), + ], + ); + } + + public function tearDown(): void + { + } + + public function testValue(): void + { + // Test for Success + $this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), true, $this->validator->getDescription()); + + // Test for Failure + $this->assertEquals($this->validator->isValid(Query::limit(1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(0)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(100)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(0)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(5000)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::equal('dne', ['v'])), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::equal('', ['v'])), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), false, $this->validator->getDescription()); + } +} diff --git a/tests/unit/Utopia/Database/Validator/Query/LimitTest.php b/tests/unit/Utopia/Database/Validator/Query/LimitTest.php new file mode 100644 index 0000000000..e37cb49624 --- /dev/null +++ b/tests/unit/Utopia/Database/Validator/Query/LimitTest.php @@ -0,0 +1,37 @@ +validator = new Limit(); + } + + public function tearDown(): void + { + } + + public function testValue(): void + { + // Test for Success + $this->assertEquals($this->validator->isValid(Query::limit(1)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(0)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(100)), true, $this->validator->getDescription()); + + // Test for Failure + $this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); + } +} diff --git a/tests/unit/Utopia/Database/Validator/Query/OffsetTest.php b/tests/unit/Utopia/Database/Validator/Query/OffsetTest.php new file mode 100644 index 0000000000..8f7010c718 --- /dev/null +++ b/tests/unit/Utopia/Database/Validator/Query/OffsetTest.php @@ -0,0 +1,41 @@ +validator = new Offset(); + } + + public function tearDown(): void + { + } + + public function testValue(): void + { + // Test for Success + $this->assertEquals($this->validator->isValid(Query::offset(1)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(0)), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(5000)), true, $this->validator->getDescription()); + + // Test for Failure + $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(100)), false, $this->validator->getDescription()); + } +} diff --git a/tests/unit/Utopia/Database/Validator/Query/OrderTest.php b/tests/unit/Utopia/Database/Validator/Query/OrderTest.php new file mode 100644 index 0000000000..e60b7ce9b9 --- /dev/null +++ b/tests/unit/Utopia/Database/Validator/Query/OrderTest.php @@ -0,0 +1,54 @@ +validator = new Order( + attributes: [ + new Document([ + 'key' => 'attr', + 'type' => Database::VAR_STRING, + 'array' => false, + ]), + ], + ); + } + + public function tearDown(): void + { + } + + public function testValue(): void + { + // Test for Success + $this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderAsc('')), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderDesc('')), true, $this->validator->getDescription()); + + // Test for Failure + $this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::equal('dne', ['v'])), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::equal('', ['v'])), false, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::orderDesc('dne')), false, $this->validator->getDescription()); + } +}