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()); } }