From 6430f9c1e028ccbad3eb3323eb3884ed11a1baa8 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 13 Apr 2023 19:33:46 +0300 Subject: [PATCH 01/75] Queries new classes --- composer.lock | 10 +- phpunit.xml | 2 +- src/Database/Validator/Document.php | 51 ++ src/Database/Validator/IndexedQueries.php | 112 +++++ src/Database/Validator/Queries.php | 179 ++----- src/Database/Validator/QueriesOrg.php | 222 +++++++++ src/Database/Validator/Query.php | 576 +++++++++++----------- src/Database/Validator/Query/Base.php | 58 +++ src/Database/Validator/Query/Cursor.php | 43 ++ src/Database/Validator/Query/Filter.php | 139 ++++++ src/Database/Validator/Query/Limit.php | 60 +++ src/Database/Validator/Query/Offset.php | 58 +++ src/Database/Validator/Query/Order.php | 65 +++ src/Database/Validator/Query/Select.php | 58 +++ tests/Database/Adapter/MariaDBTest.php | 130 ++--- tests/Database/Adapter/MongoDBTest.php | 204 ++++---- tests/Database/Adapter/MySQLTest.php | 152 +++--- tests/Database/Adapter/PostgresTest.php | 126 ++--- tests/Database/Adapter/SQLiteTest.php | 146 +++--- tests/Database/Validator/QueriesTest.php | 276 ++--------- tests/Database/Validator/QueriesTest2.php | 80 +++ 21 files changed, 1707 insertions(+), 1040 deletions(-) create mode 100644 src/Database/Validator/Document.php create mode 100644 src/Database/Validator/IndexedQueries.php create mode 100644 src/Database/Validator/QueriesOrg.php create mode 100644 src/Database/Validator/Query/Base.php create mode 100644 src/Database/Validator/Query/Cursor.php create mode 100644 src/Database/Validator/Query/Filter.php create mode 100644 src/Database/Validator/Query/Limit.php create mode 100644 src/Database/Validator/Query/Offset.php create mode 100644 src/Database/Validator/Query/Order.php create mode 100644 src/Database/Validator/Query/Select.php create mode 100644 tests/Database/Validator/QueriesTest2.php diff --git a/composer.lock b/composer.lock index 877e99f97..e34166784 100644 --- a/composer.lock +++ b/composer.lock @@ -906,16 +906,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.11", + "version": "1.10.13", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "8aa62e6ea8b58ffb650e02940e55a788cbc3fe21" + "reference": "f07bf8c6980b81bf9e49d44bd0caf2e737614a70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8aa62e6ea8b58ffb650e02940e55a788cbc3fe21", - "reference": "8aa62e6ea8b58ffb650e02940e55a788cbc3fe21", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f07bf8c6980b81bf9e49d44bd0caf2e737614a70", + "reference": "f07bf8c6980b81bf9e49d44bd0caf2e737614a70", "shasum": "" }, "require": { @@ -964,7 +964,7 @@ "type": "tidelift" } ], - "time": "2023-04-04T19:17:42+00:00" + "time": "2023-04-12T19:29:52+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/phpunit.xml b/phpunit.xml index 31b947dd6..3833748e0 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false"> + stopOnFailure="true"> ./tests/ diff --git a/src/Database/Validator/Document.php b/src/Database/Validator/Document.php new file mode 100644 index 000000000..c17ff882e --- /dev/null +++ b/src/Database/Validator/Document.php @@ -0,0 +1,51 @@ + '$id', + 'type' => Database::VAR_STRING, + 'array' => false, + ]); + $attributes[] = new UtopiaDocument([ + 'key' => '$createdAt', + 'type' => Database::VAR_DATETIME, + 'array' => false, + ]); + $attributes[] = new UtopiaDocument([ + 'key' => '$updatedAt', + 'type' => Database::VAR_DATETIME, + 'array' => false, + ]); + + $validators = [ + new Limit(), + new Offset(), + new Cursor(), + new Filter($attributes), + new Order($attributes), + new Select($attributes), + ]; + + parent::__construct($attributes, $indexes, ...$validators); + } +} diff --git a/src/Database/Validator/IndexedQueries.php b/src/Database/Validator/IndexedQueries.php new file mode 100644 index 000000000..ceaf83742 --- /dev/null +++ b/src/Database/Validator/IndexedQueries.php @@ -0,0 +1,112 @@ + + */ + protected array $attributes = []; + + /** + * @var array + */ + protected array $indexes = []; + + /** + * Expression constructor + * + * This Queries Validator filters indexes for only available indexes + * + * @param array $attributes + * @param array $indexes + * @param Base ...$validators + * @throws Exception + */ + public function __construct(array $attributes = [], array $indexes = [], Base ...$validators) + { + $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(...$validators); + } + + /** + * 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 (!parent::isValid($value)) { + return false; + } + + $queries = []; + foreach ($value as $query) { + if (!$query instanceof Query) { + $query = Query::parse($query); + } + + $queries[] = $query; + } + + $grouped = Query::groupByType($queries); + $filters = $grouped['filters']; + + foreach ($filters as $filter) { + if ($filter->getMethod() === Query::TYPE_SEARCH) { + $matched = false; + + foreach ($this->indexes as $index) { + if ( + $index->getAttribute('type') === Database::INDEX_FULLTEXT + && $index->getAttribute('attributes') === [$filter->getAttribute()] + ) { + $matched = true; + } + } + + if (!$matched) { + $this->message = "Searching by attribute \"{$filter->getAttribute()}\" requires a fulltext index."; + return false; + } + } + } + + return true; + } +} diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index b5b496c17..de60154b4 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -2,64 +2,30 @@ namespace Utopia\Database\Validator; -use Exception; -use Utopia\Database\Database; -use Utopia\Database\Document; -use Utopia\Database\Query; -use Utopia\Database\Validator\Query as QueryValidator; +use Utopia\Database\Validator\Query\Base; use Utopia\Validator; +use Utopia\Database\Query; class Queries extends Validator { - protected string $message = 'Invalid queries'; - - protected QueryValidator $validator; - /** - * @var array + * @var string */ - protected array $attributes = []; + protected string $message = 'Invalid queries'; /** - * @var array + * @var array */ - protected array $indexes = []; - protected bool $strict; + protected array $validators; /** * Queries constructor * - * @param QueryValidator $validator used to validate each query - * @param array|null $attributes allowed attributes to be queried - * @param array|null $indexes available for strict query matching - * @param bool $strict - * @throws Exception + * @param Base ...$validators a list of validators */ - public function __construct(QueryValidator $validator, ?array $attributes, ?array $indexes, bool $strict = true) + public function __construct(Base ...$validators) { - $this->validator = $validator; - $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; - } - - $this->strict = $strict; + $this->validators = $validators; } /** @@ -80,10 +46,6 @@ public function getDescription(): string * Returns false if: * 1. any query in $value is invalid based on $validator * - * In addition, if $strict is true, this returns false if: - * 1. there is no index with an exact match of the filters - * 2. there is no index with an exact match of the order attributes - * * Otherwise, returns true. * * @param mixed $value @@ -91,69 +53,59 @@ public function getDescription(): string */ 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}'; + } catch (\Throwable) { + $this->message = "Invalid query: {$query}"; return false; } } - if (!$this->validator->isValid($query)) { - $this->message = 'Query not valid: ' . $this->validator->getDescription(); - return false; - } - - $queries[] = $query; - } - - if (!$this->strict) { - return true; - } - - $grouped = Query::groupByType($queries); - /** @var array $filters */ - $filters = $grouped['filters']; - /** @var array $orderAttributes */ - $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; + $method = $query->getMethod(); + $methodType = match ($method) { + Query::TYPE_SELECT => Base::METHOD_TYPE_SELECT, + Query::TYPE_LIMIT => Base::METHOD_TYPE_LIMIT, + Query::TYPE_OFFSET => Base::METHOD_TYPE_OFFSET, + Query::TYPE_CURSORAFTER, + Query::TYPE_CURSORBEFORE => Base::METHOD_TYPE_CURSOR, + Query::TYPE_ORDERASC, + Query::TYPE_ORDERDESC => Base::METHOD_TYPE_ORDER, + Query::TYPE_EQUAL, + Query::TYPE_NOTEQUAL, + Query::TYPE_LESSER, + Query::TYPE_LESSEREQUAL, + Query::TYPE_GREATER, + Query::TYPE_GREATEREQUAL, + Query::TYPE_SEARCH, + Query::TYPE_IS_NULL, + Query::TYPE_IS_NOT_NULL, + Query::TYPE_BETWEEN, + Query::TYPE_STARTS_WITH, + Query::TYPE_ENDS_WITH => Base::METHOD_TYPE_FILTER, + default => '', + }; + + $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; } - } - if (!$found) { - $this->message = 'Index not found: ' . implode(",", array_keys($filtersByAttribute)); - return false; + $methodIsValid = true; } - // 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)); + if (!$methodIsValid) { + $this->message = 'Query method not valid: ' . $method; 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; } @@ -180,43 +132,4 @@ public function getType(): string { return self::TYPE_OBJECT; } - - /** - * Is Strict - * - * Returns true if strict validation is set - * - * @return bool - */ - public function isStrict(): bool - { - return $this->strict; - } - - /** - * 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; - } } diff --git a/src/Database/Validator/QueriesOrg.php b/src/Database/Validator/QueriesOrg.php new file mode 100644 index 000000000..dad386dff --- /dev/null +++ b/src/Database/Validator/QueriesOrg.php @@ -0,0 +1,222 @@ + + */ + protected array $attributes = []; + + /** + * @var array + */ + protected array $indexes = []; + protected bool $strict; + + /** + * Queries constructor + * + * @param QueryValidator $validator used to validate each query + * @param array|null $attributes allowed attributes to be queried + * @param array|null $indexes available for strict query matching + * @param bool $strict + * @throws Exception + */ + public function __construct(QueryValidator $validator, ?array $attributes, ?array $indexes, bool $strict = true) + { + $this->validator = $validator; + $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; + } + + $this->strict = $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 + * + * In addition, if $strict is true, this returns false if: + * 1. there is no index with an exact match of the filters + * 2. 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 + { + $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; + } + + if (!$this->strict) { + return true; + } + + $grouped = Query::groupByType($queries); + /** @var array $filters */ + $filters = $grouped['filters']; + /** @var array $orderAttributes */ + $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; + } + + /** + * 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; + } + + /** + * Is Strict + * + * Returns true if strict validation is set + * + * @return bool + */ + public function isStrict(): bool + { + return $this->strict; + } + + /** + * 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; + } +} diff --git a/src/Database/Validator/Query.php b/src/Database/Validator/Query.php index 0cd5eda61..416aee434 100644 --- a/src/Database/Validator/Query.php +++ b/src/Database/Validator/Query.php @@ -1,289 +1,289 @@ > - */ - protected array $schema = []; - - protected int $maxLimit; - protected int $maxOffset; - protected int $maxValuesCount; - - /** - * Query constructor - * - * @param array $attributes - * @param int $maxLimit - * @param int $maxOffset - * @param int $maxValuesCount - */ - public function __construct(array $attributes, int $maxLimit = 100, int $maxOffset = 5000, int $maxValuesCount = 100) - { - $this->schema['$id'] = [ - 'key' => '$id', - 'array' => false, - 'type' => Database::VAR_STRING, - 'size' => 512 - ]; - - $this->schema['$createdAt'] = [ - 'key' => '$createdAt', - 'array' => false, - 'type' => Database::VAR_DATETIME, - 'size' => 0 - ]; - - $this->schema['$updatedAt'] = [ - 'key' => '$updatedAt', - 'array' => false, - 'type' => Database::VAR_DATETIME, - 'size' => 0 - ]; - - foreach ($attributes as $attribute) { - $this->schema[(string)$attribute->getAttribute('key')] = $attribute->getArrayCopy(); - } - - $this->maxLimit = $maxLimit; - $this->maxOffset = $maxOffset; - $this->maxValuesCount = $maxValuesCount; - } - - /** - * Get Description. - * - * Returns validator description - * - * @return string - */ - public function getDescription(): string - { - return $this->message; - } - - protected function isValidLimit(?int $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(?int $offset): bool - { - $validator = new Range(0, $this->maxOffset); - if ($validator->isValid($offset)) { - return true; - } - - $this->message = 'Invalid offset: ' . $validator->getDescription(); - return false; - } - - protected function isValidCursor(?string $cursor): bool - { - if ($cursor === null) { - $this->message = 'Cursor must not be null'; - return false; - } - return true; - } - - protected function isValidAttribute(string $attribute): bool - { - // Search for attribute in schema - if (!isset($this->schema[$attribute])) { - $this->message = 'Attribute not found in schema: ' . $attribute; - return false; - } - - return true; - } - - /** - * @param string $attribute - * @param array $values - * @return bool - */ - 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) { - switch ($attributeType) { - case Database::VAR_DATETIME: - $condition = gettype($value) === Database::VAR_STRING; - break; - case Database::VAR_FLOAT: - $condition = (gettype($value) === Database::VAR_FLOAT || gettype($value) === Database::VAR_INTEGER); - break; - default: - $condition = gettype($value) === $attributeType; - break; - } - - if (!$condition) { - $this->message = 'Query type does not match expected: ' . $attributeType; - return false; - } - } - - return true; - } - - /** - * @param string $attribute - * @param array $values - * @return bool - */ - 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: ' . DatabaseQuery::TYPE_CONTAINS; - return false; - } - - return true; - } - - /** - * @param array $attributes - * @return bool - */ - protected function isValidSelect(array $attributes): bool - { - foreach ($attributes as $attribute) { - if (!$this->isValidAttribute($attribute)) { - return false; - } - } - - return true; - } - - /** - * Is valid. - * - * Returns false if: - * 1. $query has an invalid method - * 2. limit value is not a number, less than 0, or greater than $maxLimit - * 3. offset value is not a number, less than 0, or greater than $maxOffset - * 4. attribute does not exist - * 5. count of values is greater than $maxValuesCount - * 6. value type does not match attribute type - * 6. contains method is used on non-array attribute - * - * Otherwise, returns true. - * - * @param DatabaseQuery $query - * - * @return bool - */ - public function isValid($query): bool - { - // Validate method - $method = $query->getMethod(); - if (!DatabaseQuery::isMethod($method)) { - $this->message = 'Query method invalid: ' . $method; - return false; - } - - $attribute = $query->getAttribute(); - - switch ($method) { - case DatabaseQuery::TYPE_LIMIT: - $limit = $query->getValue(); - return $this->isValidLimit($limit); - - case DatabaseQuery::TYPE_OFFSET: - $offset = $query->getValue(); - return $this->isValidOffset($offset); - - case DatabaseQuery::TYPE_CURSORAFTER: - case DatabaseQuery::TYPE_CURSORBEFORE: - $cursor = $query->getValue(); - return $this->isValidCursor($cursor); - - case DatabaseQuery::TYPE_ORDERASC: - case DatabaseQuery::TYPE_ORDERDESC: - // Allow empty string for order attribute so we can order by natural order - if ($attribute === '') { - return true; - } - return $this->isValidAttribute($attribute); - - case DatabaseQuery::TYPE_CONTAINS: - $values = $query->getValues(); - return $this->isValidContains($attribute, $values); - - case DatabaseQuery::TYPE_SELECT: - $attributes = $query->getValues(); - return $this->isValidSelect($attributes); - - default: - // other filter queries - $values = $query->getValues(); - return $this->isValidAttributeAndValues($attribute, $values); - } - } - /** - * 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; - } -} +// +//namespace Utopia\Database\Validator; +// +//use Utopia\Database\Database; +//use Utopia\Validator; +//use Utopia\Validator\Range; +//use Utopia\Database\Document; +//use Utopia\Database\Query as DatabaseQuery; +// +//class Query extends Validator +//{ +// /** +// * @var string +// */ +// protected string $message = 'Invalid query'; +// +// /** +// * @var array> +// */ +// protected array $schema = []; +// +// protected int $maxLimit; +// protected int $maxOffset; +// protected int $maxValuesCount; +// +// /** +// * Query constructor +// * +// * @param array $attributes +// * @param int $maxLimit +// * @param int $maxOffset +// * @param int $maxValuesCount +// */ +// public function __construct(array $attributes, int $maxLimit = 100, int $maxOffset = 5000, int $maxValuesCount = 100) +// { +// $this->schema['$id'] = [ +// 'key' => '$id', +// 'array' => false, +// 'type' => Database::VAR_STRING, +// 'size' => 512 +// ]; +// +// $this->schema['$createdAt'] = [ +// 'key' => '$createdAt', +// 'array' => false, +// 'type' => Database::VAR_DATETIME, +// 'size' => 0 +// ]; +// +// $this->schema['$updatedAt'] = [ +// 'key' => '$updatedAt', +// 'array' => false, +// 'type' => Database::VAR_DATETIME, +// 'size' => 0 +// ]; +// +// foreach ($attributes as $attribute) { +// $this->schema[(string)$attribute->getAttribute('key')] = $attribute->getArrayCopy(); +// } +// +// $this->maxLimit = $maxLimit; +// $this->maxOffset = $maxOffset; +// $this->maxValuesCount = $maxValuesCount; +// } +// +// /** +// * Get Description. +// * +// * Returns validator description +// * +// * @return string +// */ +// public function getDescription(): string +// { +// return $this->message; +// } +// +// protected function isValidLimit(?int $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(?int $offset): bool +// { +// $validator = new Range(0, $this->maxOffset); +// if ($validator->isValid($offset)) { +// return true; +// } +// +// $this->message = 'Invalid offset: ' . $validator->getDescription(); +// return false; +// } +// +// protected function isValidCursor(?string $cursor): bool +// { +// if ($cursor === null) { +// $this->message = 'Cursor must not be null'; +// return false; +// } +// return true; +// } +// +// protected function isValidAttribute(string $attribute): bool +// { +// // Search for attribute in schema +// if (!isset($this->schema[$attribute])) { +// $this->message = 'Attribute not found in schema: ' . $attribute; +// return false; +// } +// +// return true; +// } +// +// /** +// * @param string $attribute +// * @param array $values +// * @return bool +// */ +// 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) { +// switch ($attributeType) { +// case Database::VAR_DATETIME: +// $condition = gettype($value) === Database::VAR_STRING; +// break; +// case Database::VAR_FLOAT: +// $condition = (gettype($value) === Database::VAR_FLOAT || gettype($value) === Database::VAR_INTEGER); +// break; +// default: +// $condition = gettype($value) === $attributeType; +// break; +// } +// +// if (!$condition) { +// $this->message = 'Query type does not match expected: ' . $attributeType; +// return false; +// } +// } +// +// return true; +// } +// +// /** +// * @param string $attribute +// * @param array $values +// * @return bool +// */ +// 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: ' . DatabaseQuery::TYPE_CONTAINS; +// return false; +// } +// +// return true; +// } +// +// /** +// * @param array $attributes +// * @return bool +// */ +// protected function isValidSelect(array $attributes): bool +// { +// foreach ($attributes as $attribute) { +// if (!$this->isValidAttribute($attribute)) { +// return false; +// } +// } +// +// return true; +// } +// +// /** +// * Is valid. +// * +// * Returns false if: +// * 1. $query has an invalid method +// * 2. limit value is not a number, less than 0, or greater than $maxLimit +// * 3. offset value is not a number, less than 0, or greater than $maxOffset +// * 4. attribute does not exist +// * 5. count of values is greater than $maxValuesCount +// * 6. value type does not match attribute type +// * 6. contains method is used on non-array attribute +// * +// * Otherwise, returns true. +// * +// * @param DatabaseQuery $query +// * +// * @return bool +// */ +// public function isValid($query): bool +// { +// // Validate method +// $method = $query->getMethod(); +// if (!DatabaseQuery::isMethod($method)) { +// $this->message = 'Query method invalid: ' . $method; +// return false; +// } +// +// $attribute = $query->getAttribute(); +// +// switch ($method) { +// case DatabaseQuery::TYPE_LIMIT: +// $limit = $query->getValue(); +// return $this->isValidLimit($limit); +// +// case DatabaseQuery::TYPE_OFFSET: +// $offset = $query->getValue(); +// return $this->isValidOffset($offset); +// +// case DatabaseQuery::TYPE_CURSORAFTER: +// case DatabaseQuery::TYPE_CURSORBEFORE: +// $cursor = $query->getValue(); +// return $this->isValidCursor($cursor); +// +// case DatabaseQuery::TYPE_ORDERASC: +// case DatabaseQuery::TYPE_ORDERDESC: +// // Allow empty string for order attribute so we can order by natural order +// if ($attribute === '') { +// return true; +// } +// return $this->isValidAttribute($attribute); +// +// case DatabaseQuery::TYPE_CONTAINS: +// $values = $query->getValues(); +// return $this->isValidContains($attribute, $values); +// +// case DatabaseQuery::TYPE_SELECT: +// $attributes = $query->getValues(); +// return $this->isValidSelect($attributes); +// +// default: +// // other filter queries +// $values = $query->getValues(); +// return $this->isValidAttributeAndValues($attribute, $values); +// } +// } +// /** +// * 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/Database/Validator/Query/Base.php b/src/Database/Validator/Query/Base.php new file mode 100644 index 000000000..a37fdd65a --- /dev/null +++ b/src/Database/Validator/Query/Base.php @@ -0,0 +1,58 @@ +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; + } + + /** + * Returns what type of query this Validator is for + */ + abstract public function getMethodType(): string; +} diff --git a/src/Database/Validator/Query/Cursor.php b/src/Database/Validator/Query/Cursor.php new file mode 100644 index 000000000..824cd61ad --- /dev/null +++ b/src/Database/Validator/Query/Cursor.php @@ -0,0 +1,43 @@ +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; + } + + public function getMethodType(): string + { + return self::METHOD_TYPE_CURSOR; + } +} diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php new file mode 100644 index 000000000..1e650f12b --- /dev/null +++ b/src/Database/Validator/Query/Filter.php @@ -0,0 +1,139 @@ +schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); + } + + $this->maxValuesCount = $maxValuesCount; + } + + protected function isValidAttribute($attribute): bool + { + if (\str_contains($attribute, '.')) { + // For relationships, just validate the top level. + // Utopia will validate each nested level during the recursive calls. + $attribute = \explode('.', $attribute)[0]; + + // TODO: Remove this when nested queries are supported + if (isset($this->schema[$attribute])) { + $this->message = 'Cannot query nested attribute on: ' . $attribute; + return false; + } + } + + // 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; + } + + if (\str_contains($attribute, '.')) { + // For relationships, just validate the top level. + // Utopia will validate each nested level during the recursive calls. + $attribute = \explode('.', $attribute)[0]; + } + + $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_RELATIONSHIP => true, + Database::VAR_DATETIME => gettype($value) === Database::VAR_STRING, + Database::VAR_FLOAT => (gettype($value) === Database::VAR_FLOAT || gettype($value) === Database::VAR_INTEGER), + default => gettype($value) === $attributeType + }; + + if (!$condition) { + $this->message = 'Query type does not match expected: ' . $attributeType; + return false; + } + } + + return true; + } + + /** + * Is valid. + * + * Returns true if method is a filter method, attribute exists, and value matches attribute type + * + * Otherwise, returns false + * + * @param $query + * @return bool + */ + public function isValid($query): bool + { + // Validate method + $method = $query->getMethod(); + $attribute = $query->getAttribute(); + + switch ($method) { + 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: + case Query::TYPE_STARTS_WITH: + case Query::TYPE_ENDS_WITH: + case Query::TYPE_BETWEEN: + case Query::TYPE_IS_NULL: + case Query::TYPE_IS_NOT_NULL: + $values = $query->getValues(); + return $this->isValidAttributeAndValues($attribute, $values); + + default: + return false; + } + } + + public function getMethodType(): string + { + return self::METHOD_TYPE_FILTER; + } +} diff --git a/src/Database/Validator/Query/Limit.php b/src/Database/Validator/Query/Limit.php new file mode 100644 index 000000000..1effe1365 --- /dev/null +++ b/src/Database/Validator/Query/Limit.php @@ -0,0 +1,60 @@ +maxLimit = $maxLimit; + } + + 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::TYPE_LIMIT) { + $this->message = 'Query method invalid: ' . $method; + return false; + } + + $limit = $query->getValue(); + return $this->isValidLimit($limit); + } + + public function getMethodType(): string + { + return self::METHOD_TYPE_LIMIT; + } +} diff --git a/src/Database/Validator/Query/Offset.php b/src/Database/Validator/Query/Offset.php new file mode 100644 index 000000000..476d21437 --- /dev/null +++ b/src/Database/Validator/Query/Offset.php @@ -0,0 +1,58 @@ +maxOffset = $maxOffset; + } + + 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 offset and values are within range. + * + * @param Query $value + * + * @return bool + */ + public function isValid($query): bool + { + // Validate method + $method = $query->getMethod(); + + if ($method !== Query::TYPE_OFFSET) { + $this->message = 'Query method invalid: ' . $method; + return false; + } + + $offset = $query->getValue(); + return $this->isValidOffset($offset); + } + + public function getMethodType(): string + { + return self::METHOD_TYPE_OFFSET; + } +} diff --git a/src/Database/Validator/Query/Order.php b/src/Database/Validator/Query/Order.php new file mode 100644 index 000000000..db775a05a --- /dev/null +++ b/src/Database/Validator/Query/Order.php @@ -0,0 +1,65 @@ +schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); + } + } + + 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; + } + + /** + * Is valid. + * + * Returns true if method is ORDER_ASC or ORDER_DESC and attributes are valid + * + * 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 false; + } + + public function getMethodType(): string + { + return self::METHOD_TYPE_ORDER; + } +} diff --git a/src/Database/Validator/Query/Select.php b/src/Database/Validator/Query/Select.php new file mode 100644 index 000000000..591c53889 --- /dev/null +++ b/src/Database/Validator/Query/Select.php @@ -0,0 +1,58 @@ +schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); + } + } + + /** + * Is valid. + * + * Returns true if method is TYPE_SELECT selections are valid + * + * Otherwise, returns false + * + * @param $query + * @return bool + */ + public function isValid($query): bool + { + /* @var $query Query */ + + if ($query->getMethod() !== Query::TYPE_SELECT) { + return false; + } + + foreach ($query->getValues() as $attribute) { + if (\str_contains($attribute, '.')) { + // For relationships, just validate the top level. + // Utopia will validate each nested level during the recursive calls. + $attribute = \explode('.', $attribute)[0]; + } + if (!isset($this->schema[$attribute]) && $attribute !== '*') { + $this->message = 'Attribute not found in schema: ' . $attribute; + return false; + } + } + return true; + } + + public function getMethodType(): string + { + return self::METHOD_TYPE_SELECT; + } +} diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php index 9cc3fc781..f14b2882f 100644 --- a/tests/Database/Adapter/MariaDBTest.php +++ b/tests/Database/Adapter/MariaDBTest.php @@ -1,66 +1,66 @@ connect('redis', 6379); - $redis->flushAll(); - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new MariaDB($pdo), $cache); - $database->setDefaultDatabase('utopiaTests'); - $database->setNamespace('myapp_'.uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } - - public static function killDatabase(): void - { - self::$database = null; - } -} +// +//namespace Utopia\Tests\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Database\Database; +//use Utopia\Database\Adapter\MariaDB; +//use Utopia\Cache\Cache; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Tests\Base; +// +//class MariaDBTest extends Base +//{ +// public static ?Database $database = null; +// +// // TODO@kodumbeats hacky way to identify adapters for tests +// // Remove once all methods are implemented +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "mariadb"; +// } +// +// /** +// * @return Database +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $dbHost = 'mariadb'; +// $dbPort = '3306'; +// $dbUser = 'root'; +// $dbPass = 'password'; +// +// $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MariaDB::getPDOAttributes()); +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new MariaDB($pdo), $cache); +// $database->setDefaultDatabase('utopiaTests'); +// $database->setNamespace('myapp_'.uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +// +// public static function killDatabase(): void +// { +// self::$database = null; +// } +//} diff --git a/tests/Database/Adapter/MongoDBTest.php b/tests/Database/Adapter/MongoDBTest.php index 563837006..646318a71 100644 --- a/tests/Database/Adapter/MongoDBTest.php +++ b/tests/Database/Adapter/MongoDBTest.php @@ -1,103 +1,103 @@ connect('redis', 6379); - $redis->flushAll(); - $cache = new Cache(new RedisAdapter($redis)); - - $schema = 'utopiaTests'; // same as $this->testDatabase - $client = new Client( - $schema, - 'mongo', - 27017, - 'root', - 'example', - false - ); - - $database = new Database(new Mongo($client), $cache); - $database->setDefaultDatabase($schema); - $database->setNamespace('myapp_' . uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } - - /** - * @throws Exception - */ - public function testCreateExistsDelete(): void - { - // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. - $this->assertNotNull(static::getDatabase()->create()); - $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); - $this->assertEquals(true, static::getDatabase()->create()); - $this->assertEquals(true, static::getDatabase()->setDefaultDatabase($this->testDatabase)); - } - - public function testRenameAttribute(): void - { - $this->assertTrue(true); - } - - public function testRenameAttributeExisting(): void - { - $this->assertTrue(true); - } - - public function testUpdateAttributeStructure(): void - { - $this->assertTrue(true); - } - - public function testKeywords(): void - { - $this->assertTrue(true); - } - - public static function killDatabase(): void - { - self::$database = null; - } -} +// +//namespace Utopia\Tests\Adapter; +// +//use Exception; +//use Redis; +//use Utopia\Cache\Cache; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Database\Adapter\Mongo; +//use Utopia\Database\Database; +//use Utopia\Mongo\Client; +//use Utopia\Tests\Base; +// +//class MongoDBTest extends Base +//{ +// public static ?Database $database = null; +// +// +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "mongodb"; +// } +// +// /** +// * @return Database +// * @throws Exception +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// $cache = new Cache(new RedisAdapter($redis)); +// +// $schema = 'utopiaTests'; // same as $this->testDatabase +// $client = new Client( +// $schema, +// 'mongo', +// 27017, +// 'root', +// 'example', +// false +// ); +// +// $database = new Database(new Mongo($client), $cache); +// $database->setDefaultDatabase($schema); +// $database->setNamespace('myapp_' . uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +// +// /** +// * @throws Exception +// */ +// public function testCreateExistsDelete(): void +// { +// // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. +// $this->assertNotNull(static::getDatabase()->create()); +// $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); +// $this->assertEquals(true, static::getDatabase()->create()); +// $this->assertEquals(true, static::getDatabase()->setDefaultDatabase($this->testDatabase)); +// } +// +// public function testRenameAttribute(): void +// { +// $this->assertTrue(true); +// } +// +// public function testRenameAttributeExisting(): void +// { +// $this->assertTrue(true); +// } +// +// public function testUpdateAttributeStructure(): void +// { +// $this->assertTrue(true); +// } +// +// public function testKeywords(): void +// { +// $this->assertTrue(true); +// } +// +// public static function killDatabase(): void +// { +// self::$database = null; +// } +//} diff --git a/tests/Database/Adapter/MySQLTest.php b/tests/Database/Adapter/MySQLTest.php index f0ee5822b..fd5cf3232 100644 --- a/tests/Database/Adapter/MySQLTest.php +++ b/tests/Database/Adapter/MySQLTest.php @@ -1,77 +1,77 @@ connect('redis', 6379); - $redis->flushAll(); - - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new MySQL($pdo), $cache); - $database->setDefaultDatabase('utopiaTests'); - $database->setNamespace('myapp_'.uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } - - public static function killDatabase(): void - { - self::$database = null; - } -} +// +//namespace Utopia\Tests\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Cache\Cache; +//use Utopia\Database\Database; +//use Utopia\Database\Adapter\MySQL; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Tests\Base; +// +//class MySQLTest extends Base +//{ +// public static ?Database $database = null; +// +// // TODO@kodumbeats hacky way to identify adapters for tests +// // Remove once all methods are implemented +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "mysql"; +// } +// +// /** +// * +// * @return int +// */ +// public static function getUsedIndexes(): int +// { +// return MySQL::getCountOfDefaultIndexes(); +// } +// +// /** +// * @return Database +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $dbHost = 'mysql'; +// $dbPort = '3307'; +// $dbUser = 'root'; +// $dbPass = 'password'; +// +// $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MySQL::getPDOAttributes()); +// +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new MySQL($pdo), $cache); +// $database->setDefaultDatabase('utopiaTests'); +// $database->setNamespace('myapp_'.uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +// +// public static function killDatabase(): void +// { +// self::$database = null; +// } +//} diff --git a/tests/Database/Adapter/PostgresTest.php b/tests/Database/Adapter/PostgresTest.php index 4b5451341..a635ef7f0 100644 --- a/tests/Database/Adapter/PostgresTest.php +++ b/tests/Database/Adapter/PostgresTest.php @@ -1,64 +1,64 @@ connect('redis', 6379); - $redis->flushAll(); - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new Postgres($pdo), $cache); - $database->setDefaultDatabase('utopiaTests'); - $database->setNamespace('myapp_'.uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } - - public static function killDatabase(): void - { - self::$database = null; - } -} +// +//namespace Utopia\Tests\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Database\Database; +//use Utopia\Database\Adapter\Postgres; +//use Utopia\Cache\Cache; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Tests\Base; +// +//class PostgresTest extends Base +//{ +// public static ?Database $database = null; +// +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "postgres"; +// } +// +// /** +// * @reture Adapter +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $dbHost = 'postgres'; +// $dbPort = '5432'; +// $dbUser = 'root'; +// $dbPass = 'password'; +// +// $pdo = new PDO("pgsql:host={$dbHost};port={$dbPort};", $dbUser, $dbPass, Postgres::getPDOAttributes()); +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new Postgres($pdo), $cache); +// $database->setDefaultDatabase('utopiaTests'); +// $database->setNamespace('myapp_'.uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +// +// public static function killDatabase(): void +// { +// self::$database = null; +// } +//} diff --git a/tests/Database/Adapter/SQLiteTest.php b/tests/Database/Adapter/SQLiteTest.php index b704d58e1..7afc3b63d 100644 --- a/tests/Database/Adapter/SQLiteTest.php +++ b/tests/Database/Adapter/SQLiteTest.php @@ -1,74 +1,74 @@ connect('redis', 6379); - $redis->flushAll(); - - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new SQLite($pdo), $cache); - $database->setDefaultDatabase('utopiaTests'); - $database->setNamespace('myapp_'.uniqid()); - - return self::$database = $database; - } - - public static function killDatabase(): void - { - self::$database = null; - } -} +// +//namespace Utopia\Tests\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Cache\Cache; +//use Utopia\Database\Database; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Database\Adapter\SQLite; +//use Utopia\Tests\Base; +// +//class SQLiteTest extends Base +//{ +// public static ?Database $database = null; +// +// // TODO@kodumbeats hacky way to identify adapters for tests +// // Remove once all methods are implemented +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "sqlite"; +// } +// +// /** +// * +// * @return int +// */ +// public static function getUsedIndexes(): int +// { +// return SQLite::getCountOfDefaultIndexes(); +// } +// +// /** +// * @return Database +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $sqliteDir = __DIR__."/database.sql"; +// +// if (file_exists($sqliteDir)) { +// unlink($sqliteDir); +// } +// +// $dsn = $sqliteDir; +// $dsn = 'memory'; // Overwrite for fast tests +// $pdo = new PDO("sqlite:" . $dsn, null, null, SQLite::getPDOAttributes()); +// +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new SQLite($pdo), $cache); +// $database->setDefaultDatabase('utopiaTests'); +// $database->setNamespace('myapp_'.uniqid()); +// +// return self::$database = $database; +// } +// +// public static function killDatabase(): void +// { +// self::$database = null; +// } +//} diff --git a/tests/Database/Validator/QueriesTest.php b/tests/Database/Validator/QueriesTest.php index ba5910be2..261394e67 100644 --- a/tests/Database/Validator/QueriesTest.php +++ b/tests/Database/Validator/QueriesTest.php @@ -2,13 +2,14 @@ namespace Utopia\Tests\Validator; +use Exception; use Utopia\Database\Helpers\ID; -use Utopia\Database\Validator\Query; use PHPUnit\Framework\TestCase; use Utopia\Database\Database; use Utopia\Database\Document; -use Utopia\Database\Query as DatabaseQuery; +use Utopia\Database\Query; use Utopia\Database\Validator\Queries; +use Utopia\Database\Validator\Document as DocumentValidator; class QueriesTest extends TestCase { @@ -18,14 +19,7 @@ class QueriesTest extends TestCase protected array $collection = []; /** - * @var array - */ - protected array $queries = []; - - protected ?Query $queryValidator = null; - - /** - * @throws \Exception + * @throws Exception */ public function setUp(): void { @@ -73,250 +67,64 @@ public function setUp(): void 'signed' => true, 'array' => false, 'filters' => [], - ]), - new Document([ - '$id' => 'published', - 'key' => 'published', - 'type' => Database::VAR_BOOLEAN, - 'size' => 5, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]), - new Document([ - '$id' => 'tags', - 'key' => 'tags', - 'type' => Database::VAR_STRING, - 'size' => 55, - 'required' => true, - 'signed' => true, - 'array' => true, - 'filters' => [], - ]), - ], - 'indexes' => [], - ]; - - $this->queryValidator = new Query($this->collection['attributes']); - - $query1 = 'notEqual("title", ["Iron Man", "Ant Man"])'; - $query2 = 'equal("description", "Best movie ever")'; - - array_push($this->queries, $query1, $query2); - - // Constructor expects array $indexes - // Object property declaration cannot initialize a Document object - // Add array $indexes separately - $index1 = new Document([ - '$id' => ID::custom('testindex'), - 'type' => 'key', - 'attributes' => [ - 'title', - 'description' - ], - 'orders' => [ - 'ASC', - 'DESC' - ], - ]); - - $index2 = new Document([ - '$id' => ID::custom('testindex2'), - 'type' => 'key', - 'attributes' => [ - 'title', - 'description', - 'price' - ], - 'orders' => [ - 'ASC', - 'DESC' - ], - ]); - - $index3 = new Document([ - '$id' => ID::custom('testindex3'), - 'type' => 'fulltext', - 'attributes' => [ - 'title' - ], - 'orders' => [] - ]); - - $index4 = new Document([ - '$id' => 'testindex4', - 'type' => 'key', - 'attributes' => [ - 'description' - ], - 'orders' => [] - ]); - - $this->collection['indexes'] = [$index1, $index2, $index3, $index4]; - } - - public function tearDown(): void - { - } - - /** - * @throws \Exception - */ - public function testQueries(): void - { - // test for SUCCESS - $validator = new Queries($this->queryValidator, $this->collection['attributes'], $this->collection['indexes']); - - $this->assertEquals(true, $validator->isValid($this->queries), $validator->getDescription()); - - $this->queries[] = 'lessThan("price", 6.50)'; - $this->queries[] = 'greaterThanEqual("price", 5.50)'; - $this->assertEquals(true, $validator->isValid($this->queries)); - - $queries = [DatabaseQuery::orderDesc('')]; - $this->assertEquals(true, $validator->isValid($queries), $validator->getDescription()); - - // test for FAILURE - - $this->queries[] = 'greaterThan("rating", 4)'; - - $this->assertFalse($validator->isValid($this->queries)); - $this->assertEquals("Index not found: title,description,price,rating", $validator->getDescription()); - - // test for queued index - $query1 = 'lessThan("price", 6.50)'; - $query2 = 'notEqual("title", ["Iron Man", "Ant Man"])'; - - $this->queries = [$query1, $query2]; - $this->assertEquals(false, $validator->isValid($this->queries)); - $this->assertEquals("Index not found: price,title", $validator->getDescription()); - - // test fulltext - - $query3 = 'search("description", "iron")'; - $this->queries = [$query3]; - $this->assertEquals(false, $validator->isValid($this->queries)); - $this->assertEquals("Search method requires fulltext index: description", $validator->getDescription()); - } - - /** - * @throws \Exception - */ - public function testLooseOrderQueries(): void - { - $validator = new Queries( - $this->queryValidator, - [ - new Document([ - '$id' => 'title', - 'key' => 'title', - 'type' => Database::VAR_STRING, - 'size' => 256, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]), - new Document([ - '$id' => 'rating', - 'key' => 'rating', - 'type' => Database::VAR_INTEGER, - 'size' => 5, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]), - new Document([ - '$id' => 'price', - 'key' => 'price', - 'type' => Database::VAR_FLOAT, - 'size' => 5, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]), + ]) ], - [ + 'indexes' => [ new Document([ - '$id' => 'testindex5', + '$id' => ID::custom('testindex2'), 'type' => 'key', 'attributes' => [ 'title', - 'price', - 'rating' + 'description', + 'price' + ], + 'orders' => [ + 'ASC', + 'DESC' ], - 'orders' => [] ]), new Document([ - '$id' => 'key-price', - 'type' => 'key', + '$id' => ID::custom('testindex3'), + 'type' => 'fulltext', 'attributes' => [ - 'price' + 'title' ], 'orders' => [] - ]) + ]), ], - true, - ); - - // Test for SUCCESS - $this->assertTrue($validator->isValid([ - 'lessThanEqual("price", 6.50)', - 'lessThanEqual("title", "string")', - 'lessThanEqual("rating", 2002)', - ])); - - $this->assertTrue($validator->isValid([ - 'lessThanEqual("price", 6.50)', - 'lessThanEqual("title", "string")', - 'lessThanEqual("rating", 2002)', - ])); - - $this->assertTrue($validator->isValid([ - 'lessThanEqual("price", 6.50)', - 'lessThanEqual("rating", 2002)', - 'lessThanEqual("title", "string")', - ])); - - $this->assertTrue($validator->isValid([ - 'lessThanEqual("title", "string")', - 'lessThanEqual("price", 6.50)', - 'lessThanEqual("rating", 2002)', - ])); - - $this->assertTrue($validator->isValid([ - 'lessThanEqual("title", "string")', - 'lessThanEqual("rating", 2002)', - 'lessThanEqual("price", 6.50)', - ])); - - $this->assertTrue($validator->isValid([ - 'lessThanEqual("rating", 2002)', - 'lessThanEqual("title", "string")', - 'lessThanEqual("price", 6.50)', - ])); + ]; + } - $this->assertTrue($validator->isValid([ - 'lessThanEqual("rating", 2002)', - 'lessThanEqual("price", 6.50)', - 'lessThanEqual("title", "string")', - ])); + public function tearDown(): void + { } /** - * @throws \Exception + * @throws Exception */ - public function testIsStrict(): void + public function testQueries(): void { - $validator = new Queries($this->queryValidator, $this->collection['attributes'], $this->collection['indexes']); + $validator = new DocumentValidator($this->collection['attributes'], $this->collection['indexes']); + + $queries = [ + 'notEqual("title", ["Iron Man", "Ant Man"])', + 'equal("description", "Best movie ever")', + 'lessThan("price", 6.50)', + 'greaterThan("rating", 4)', + 'between("price", 1.50, 6.50)', + 'orderAsc("title")', + ]; - $this->assertEquals(true, $validator->isStrict()); + $queries[] = Query::orderDesc(''); + $this->assertEquals(true, $validator->isValid($queries)); - $validator = new Queries($this->queryValidator, $this->collection['attributes'], $this->collection['indexes'], false); + $queries = ['search("description", "iron")']; + $this->assertFalse($validator->isValid($queries)); + $this->assertEquals('Searching by attribute "description" requires a fulltext index.', $validator->getDescription()); - $this->assertEquals(false, $validator->isStrict()); + $queries = ['equal("not_found", 4)']; + $this->assertEquals(false, $validator->isValid($queries)); + $this->assertEquals('Query not valid: Attribute not found in schema: not_found', $validator->getDescription()); } + } diff --git a/tests/Database/Validator/QueriesTest2.php b/tests/Database/Validator/QueriesTest2.php new file mode 100644 index 000000000..b27b65475 --- /dev/null +++ b/tests/Database/Validator/QueriesTest2.php @@ -0,0 +1,80 @@ +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)'])); + } + + /** + * @throws Exception + */ + 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()); + } +} From c80a4b4abe9e0535516da106a457d9f8ae14e04a Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 13 Apr 2023 20:26:11 +0300 Subject: [PATCH 02/75] Queries Tests --- tests/Database/Validator/QueriesTest.php | 39 +++++++++++++++++++++-- tests/Database/Validator/QueriesTest2.php | 2 ++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/tests/Database/Validator/QueriesTest.php b/tests/Database/Validator/QueriesTest.php index 261394e67..8a4ed517e 100644 --- a/tests/Database/Validator/QueriesTest.php +++ b/tests/Database/Validator/QueriesTest.php @@ -8,7 +8,6 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; -use Utopia\Database\Validator\Queries; use Utopia\Database\Validator\Document as DocumentValidator; class QueriesTest extends TestCase @@ -102,17 +101,28 @@ public function tearDown(): void /** * @throws Exception */ - public function testQueries(): void + public function testValidQueries(): void { $validator = new DocumentValidator($this->collection['attributes'], $this->collection['indexes']); $queries = [ 'notEqual("title", ["Iron Man", "Ant Man"])', 'equal("description", "Best movie ever")', + 'lessThanEqual("price", 6.50)', 'lessThan("price", 6.50)', 'greaterThan("rating", 4)', + 'greaterThanEqual("rating", 6)', 'between("price", 1.50, 6.50)', + 'search("title", "SEO")', + 'startsWith("title", "Good")', + 'endsWith("title", "Night")', + 'isNull("title")', + 'isNotNull("title")', + 'cursorAfter("a")', + 'cursorBefore("b")', 'orderAsc("title")', + 'limit(10)', + 'offset(10)', ]; $queries[] = Query::orderDesc(''); @@ -127,4 +137,29 @@ public function testQueries(): void $this->assertEquals('Query not valid: Attribute not found in schema: not_found', $validator->getDescription()); } + /** + * @throws Exception + */ + public function testInvalidQueries(): void + { + $validator = new DocumentValidator($this->collection['attributes'], $this->collection['indexes']); + + $queries = ['search("description", "iron")']; + $this->assertFalse($validator->isValid($queries)); + $this->assertEquals('Searching by attribute "description" requires a fulltext index.', $validator->getDescription()); + + $queries = ['equal("not_found", 4)']; + $this->assertEquals(false, $validator->isValid($queries)); + $this->assertEquals('Query not valid: Attribute not found in schema: not_found', $validator->getDescription()); + + $queries = ['limit(-1)']; + $this->assertEquals(false, $validator->isValid($queries)); + $this->assertEquals('Query not valid: Invalid limit: Value must be a valid range between 0 and 9,223,372,036,854,775,808', $validator->getDescription()); + + $queries = ['equal("title", [])']; + $this->assertEquals(false, $validator->isValid($queries)); + $this->assertEquals('We want this to fail!!!!!!', $validator->getDescription()); + } + + } diff --git a/tests/Database/Validator/QueriesTest2.php b/tests/Database/Validator/QueriesTest2.php index b27b65475..8b1ec337e 100644 --- a/tests/Database/Validator/QueriesTest2.php +++ b/tests/Database/Validator/QueriesTest2.php @@ -64,6 +64,7 @@ public function testValid(): void 'array' => false, ]) ]; + $validator = new Queries( new Cursor(), new Filter($attributes), @@ -71,6 +72,7 @@ public function testValid(): void 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()); From 9caa5597e2f9aff1ecbda8b9980dea1496088cb9 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 13 Apr 2023 20:28:06 +0300 Subject: [PATCH 03/75] Queries Tests with document validator --- tests/Database/Validator/QueriesTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Database/Validator/QueriesTest.php b/tests/Database/Validator/QueriesTest.php index 8a4ed517e..42f2edf70 100644 --- a/tests/Database/Validator/QueriesTest.php +++ b/tests/Database/Validator/QueriesTest.php @@ -158,7 +158,7 @@ public function testInvalidQueries(): void $queries = ['equal("title", [])']; $this->assertEquals(false, $validator->isValid($queries)); - $this->assertEquals('We want this to fail!!!!!!', $validator->getDescription()); + $this->assertEquals('We want this to fail!', $validator->getDescription()); } From 3457f52285e10275cdd273d8cf9c7ba5853c9817 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 23 Apr 2023 17:30:24 +0300 Subject: [PATCH 04/75] Query.php changes --- src/Database/Validator/Document.php | 5 +- src/Database/Validator/IndexedQueries.php | 11 +- src/Database/Validator/Queries.php | 28 +- src/Database/Validator/Query/Filter.php | 2 +- src/Database/Validator/Query/Limit.php | 35 +-- src/Database/Validator/Query/Offset.php | 39 ++- .../Validator/{Query.php => QueryOrg.php} | 0 tests/Database/Validator/QueriesTest.php | 11 +- tests/Database/Validator/QueryTest.php | 286 +++++++++--------- 9 files changed, 205 insertions(+), 212 deletions(-) rename src/Database/Validator/{Query.php => QueryOrg.php} (100%) diff --git a/src/Database/Validator/Document.php b/src/Database/Validator/Document.php index c17ff882e..5a9c088c5 100644 --- a/src/Database/Validator/Document.php +++ b/src/Database/Validator/Document.php @@ -2,6 +2,7 @@ namespace Utopia\Database\Validator; +use Exception; use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\Query\Filter; use Utopia\Database\Validator\Query\Limit; @@ -17,7 +18,7 @@ class Document extends IndexedQueries * Expression constructor * * @param UtopiaDocument[] $attributes - * @throws \Exception + * @throws Exception */ public function __construct(array $attributes, array $indexes) { @@ -40,7 +41,7 @@ public function __construct(array $attributes, array $indexes) $validators = [ new Limit(), new Offset(), - new Cursor(), + new Cursor(), // I think this should be checks against $attributes? new Filter($attributes), new Order($attributes), new Select($attributes), diff --git a/src/Database/Validator/IndexedQueries.php b/src/Database/Validator/IndexedQueries.php index ceaf83742..90f4da189 100644 --- a/src/Database/Validator/IndexedQueries.php +++ b/src/Database/Validator/IndexedQueries.php @@ -57,24 +57,15 @@ public function __construct(array $attributes = [], array $indexes = [], Base .. } /** - * 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 + * @throws Exception */ public function isValid($value): bool { if (!parent::isValid($value)) { return false; } - $queries = []; foreach ($value as $query) { if (!$query instanceof Query) { diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index de60154b4..1e25476a4 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -41,22 +41,21 @@ public function getDescription(): string } /** - * Is valid. - * - * Returns false if: - * 1. any query in $value is invalid based on $validator - * - * Otherwise, returns true. - * - * @param mixed $value + * @param array $value * @return bool */ public function isValid($value): bool { + if(!is_array($value)){ + $this->message = "Queries must be an array"; + return false; + } + foreach ($value as $query) { if (!$query instanceof Query) { try { $query = Query::parse($query); + } catch (\Throwable) { $this->message = "Invalid query: {$query}"; return false; @@ -83,30 +82,29 @@ public function isValid($value): bool Query::TYPE_IS_NOT_NULL, Query::TYPE_BETWEEN, Query::TYPE_STARTS_WITH, + Query::TYPE_CONTAINS, Query::TYPE_ENDS_WITH => Base::METHOD_TYPE_FILTER, default => '', }; - $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; + return true; } - if (!$methodIsValid) { - $this->message = 'Query method not valid: ' . $method; - return false; - } + $this->message = 'Query method invalid: ' . $method; + return false; } - return true; + return false; } /** diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index 1e650f12b..62d82b8b8 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -107,7 +107,6 @@ protected function isValidAttributeAndValues(string $attribute, array $values): */ public function isValid($query): bool { - // Validate method $method = $query->getMethod(); $attribute = $query->getAttribute(); @@ -124,6 +123,7 @@ public function isValid($query): bool case Query::TYPE_BETWEEN: case Query::TYPE_IS_NULL: case Query::TYPE_IS_NOT_NULL: + case Query::TYPE_CONTAINS: // todo: What to do about unsupported operators? $values = $query->getValues(); return $this->isValidAttributeAndValues($attribute, $values); diff --git a/src/Database/Validator/Query/Limit.php b/src/Database/Validator/Query/Limit.php index 1effe1365..c041c6fa1 100644 --- a/src/Database/Validator/Query/Limit.php +++ b/src/Database/Validator/Query/Limit.php @@ -3,6 +3,7 @@ namespace Utopia\Database\Validator\Query; use Utopia\Database\Query; +use Utopia\Validator\Numeric; use Utopia\Validator\Range; class Limit extends Base @@ -19,38 +20,38 @@ public function __construct(int $maxLimit = PHP_INT_MAX) $this->maxLimit = $maxLimit; } - 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 + public function isValid($value): bool { - // Validate method - $method = $query->getMethod(); + $method = $value->getMethod(); if ($method !== Query::TYPE_LIMIT) { $this->message = 'Query method invalid: ' . $method; return false; } - $limit = $query->getValue(); - return $this->isValidLimit($limit); + $limit = $value->getValue(); + + $validator = new Numeric(); + if (!$validator->isValid($limit)) { + $this->message = 'Invalid limit: ' . $validator->getDescription(); + return false; + } + + $validator = new Range(1, $this->maxLimit); + if (!$validator->isValid($limit)) { + $this->message = 'Invalid limit: ' . $validator->getDescription(); + return false; + } + + return true; } public function getMethodType(): string diff --git a/src/Database/Validator/Query/Offset.php b/src/Database/Validator/Query/Offset.php index 476d21437..a02c2ffe0 100644 --- a/src/Database/Validator/Query/Offset.php +++ b/src/Database/Validator/Query/Offset.php @@ -3,6 +3,7 @@ namespace Utopia\Database\Validator\Query; use Utopia\Database\Query; +use Utopia\Validator\Numeric; use Utopia\Validator\Range; class Offset extends Base @@ -17,38 +18,34 @@ public function __construct(int $maxOffset = PHP_INT_MAX) $this->maxOffset = $maxOffset; } - 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 offset and values are within range. - * * @param Query $value - * * @return bool */ - public function isValid($query): bool + public function isValid($value): bool { - // Validate method - $method = $query->getMethod(); + $method = $value->getMethod(); if ($method !== Query::TYPE_OFFSET) { $this->message = 'Query method invalid: ' . $method; return false; } - $offset = $query->getValue(); - return $this->isValidOffset($offset); + $offset = $value->getValue(); + + $validator = new Numeric(); + if (!$validator->isValid($offset)) { + $this->message = 'Invalid limit: ' . $validator->getDescription(); + return false; + } + + $validator = new Range(0, $this->maxOffset); + if (!$validator->isValid($offset)) { + $this->message = 'Invalid offset: ' . $validator->getDescription(); + return false; + } + + return true; } public function getMethodType(): string diff --git a/src/Database/Validator/Query.php b/src/Database/Validator/QueryOrg.php similarity index 100% rename from src/Database/Validator/Query.php rename to src/Database/Validator/QueryOrg.php diff --git a/tests/Database/Validator/QueriesTest.php b/tests/Database/Validator/QueriesTest.php index 42f2edf70..b8c5a6145 100644 --- a/tests/Database/Validator/QueriesTest.php +++ b/tests/Database/Validator/QueriesTest.php @@ -119,7 +119,7 @@ public function testValidQueries(): void 'isNull("title")', 'isNotNull("title")', 'cursorAfter("a")', - 'cursorBefore("b")', + 'cursorBefore("b")', // Todo: This should fail? 'orderAsc("title")', 'limit(10)', 'offset(10)', @@ -154,11 +154,12 @@ public function testInvalidQueries(): void $queries = ['limit(-1)']; $this->assertEquals(false, $validator->isValid($queries)); - $this->assertEquals('Query not valid: Invalid limit: Value must be a valid range between 0 and 9,223,372,036,854,775,808', $validator->getDescription()); + $this->assertEquals('Query not valid: Invalid limit: Value must be a valid range between 1 and 9,223,372,036,854,775,808', $validator->getDescription()); + +// $queries = ['equal("title", [])']; // empty array +// $this->assertEquals(false, $validator->isValid($queries)); +// $this->assertEquals('We want this to fail!', $validator->getDescription()); - $queries = ['equal("title", [])']; - $this->assertEquals(false, $validator->isValid($queries)); - $this->assertEquals('We want this to fail!', $validator->getDescription()); } diff --git a/tests/Database/Validator/QueryTest.php b/tests/Database/Validator/QueryTest.php index b60f9efe6..98e6fe515 100644 --- a/tests/Database/Validator/QueryTest.php +++ b/tests/Database/Validator/QueryTest.php @@ -2,100 +2,106 @@ namespace Utopia\Tests\Validator; -use Utopia\Database\Validator\Query; +use Exception; use PHPUnit\Framework\TestCase; use Utopia\Database\Database; use Utopia\Database\Document; -use Utopia\Database\Query as DatabaseQuery; +use Utopia\Database\Query; +use Utopia\Database\Validator\Queries; +use Utopia\Database\Validator\Query\Cursor; +use Utopia\Database\Validator\Query\Filter; +use Utopia\Database\Validator\Query\Limit; +use Utopia\Database\Validator\Query\Offset; +use Utopia\Database\Validator\Query\Order; +use Utopia\Database\Validator\Query\Select; class QueryTest extends TestCase { /** * @var array */ - protected array $schema; + protected array $attributes; /** - * @var array> + * @throws Exception */ - protected array $attributes = [ - [ - '$id' => 'title', - 'key' => 'title', - 'type' => Database::VAR_STRING, - 'size' => 256, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'description', - 'key' => 'description', - 'type' => Database::VAR_STRING, - 'size' => 1000000, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'rating', - 'key' => 'rating', - 'type' => Database::VAR_INTEGER, - 'size' => 5, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'price', - 'key' => 'price', - 'type' => Database::VAR_FLOAT, - 'size' => 5, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'published', - 'key' => 'published', - 'type' => Database::VAR_BOOLEAN, - 'size' => 5, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'tags', - 'key' => 'tags', - 'type' => Database::VAR_STRING, - 'size' => 55, - 'required' => true, - 'signed' => true, - 'array' => true, - 'filters' => [], - ], - [ - '$id' => 'birthDay', - 'key' => 'birthDay', - 'type' => Database::VAR_DATETIME, - 'size' => 0, - 'required' => false, - 'signed' => false, - 'array' => false, - 'filters' => ['datetime'], - ], - ]; - public function setUp(): void { - // Query validator expects array - foreach ($this->attributes as $attribute) { - $this->schema[] = new Document($attribute); + $attributes = [ + [ + '$id' => 'title', + 'key' => 'title', + 'type' => Database::VAR_STRING, + 'size' => 256, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'description', + 'key' => 'description', + 'type' => Database::VAR_STRING, + 'size' => 1000000, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'rating', + 'key' => 'rating', + 'type' => Database::VAR_INTEGER, + 'size' => 5, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'price', + 'key' => 'price', + 'type' => Database::VAR_FLOAT, + 'size' => 5, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'published', + 'key' => 'published', + 'type' => Database::VAR_BOOLEAN, + 'size' => 5, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'tags', + 'key' => 'tags', + 'type' => Database::VAR_STRING, + 'size' => 55, + 'required' => true, + 'signed' => true, + 'array' => true, + 'filters' => [], + ], + [ + '$id' => 'birthDay', + 'key' => 'birthDay', + 'type' => Database::VAR_DATETIME, + 'size' => 0, + 'required' => false, + 'signed' => false, + 'array' => false, + 'filters' => ['datetime'], + ], + ]; + + foreach ($attributes as $attribute) { + $this->attributes[] = new Document($attribute); } } @@ -105,139 +111,137 @@ public function tearDown(): void public function testQuery(): void { - $validator = new Query($this->schema); - - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('equal("$id", ["Iron Man", "Ant Man"])'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('notEqual("title", ["Iron Man", "Ant Man"])'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('equal("description", "Best movie ever")'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('greaterThan("rating", 4)'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('lessThan("price", 6.50)'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('lessThanEqual("price", 6)'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('contains("tags", "action")'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('cursorAfter("docId")'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('cursorBefore("docId")'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('orderAsc("title")'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('orderDesc("title")'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('isNull("title")'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('isNotNull("title")'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('between("price", 1.5, 10.9)'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('between("birthDay","2024-01-01", "2023-01-01")'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('startsWith("title", "Fro")'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('endsWith("title", "Zen")'))); - $this->assertEquals(true, $validator->isValid(DatabaseQuery::parse('select(["title", "description"])'))); + $validator = new \Utopia\Database\Validator\Document($this->attributes, []); + + $this->assertEquals(true, $validator->isValid([Query::parse('equal("$id", ["Iron Man", "Ant Man"])')])); + $this->assertEquals(true, $validator->isValid([Query::parse('notEqual("title", ["Iron Man", "Ant Man"])')])); + $this->assertEquals(true, $validator->isValid([Query::parse('equal("description", "Best movie ever")')])); + $this->assertEquals(true, $validator->isValid([Query::parse('greaterThan("rating", 4)')])); + $this->assertEquals(true, $validator->isValid([Query::parse('lessThan("price", 6.50)')])); + $this->assertEquals(true, $validator->isValid([Query::parse('lessThanEqual("price", 6)')])); + $this->assertEquals(true, $validator->isValid([Query::parse('contains("tags", "action")')])); + $this->assertEquals(true, $validator->isValid([Query::parse('cursorAfter("docId")')])); + $this->assertEquals(true, $validator->isValid([Query::parse('cursorBefore("docId")')])); + $this->assertEquals(true, $validator->isValid([Query::parse('orderAsc("title")')])); + $this->assertEquals(true, $validator->isValid([Query::parse('orderDesc("title")')])); + $this->assertEquals(true, $validator->isValid([Query::parse('isNull("title")')])); + $this->assertEquals(true, $validator->isValid([Query::parse('isNotNull("title")')])); + $this->assertEquals(true, $validator->isValid([Query::parse('between("price", 1.5, 10.9)')])); + $this->assertEquals(true, $validator->isValid([Query::parse('between("birthDay","2024-01-01", "2023-01-01")')])); + $this->assertEquals(true, $validator->isValid([Query::parse('startsWith("title", "Fro")')])); + $this->assertEquals(true, $validator->isValid([Query::parse('endsWith("title", "Zen")')])); + $this->assertEquals(true, $validator->isValid([Query::parse('select(["title", "description"])')])); } public function testInvalidMethod(): void { - $validator = new Query($this->schema); - - $response = $validator->isValid(DatabaseQuery::parse('eqqual("title", "Iron Man")')); + $validator = new \Utopia\Database\Validator\Document($this->attributes, []); - $this->assertEquals(false, $response); + $this->assertEquals(false, $validator->isValid([Query::parse('eqqual("title", "Iron Man")')])); $this->assertEquals('Query method invalid: eqqual', $validator->getDescription()); } public function testAttributeNotFound(): void { - $validator = new Query($this->schema); + $validator = new \Utopia\Database\Validator\Document($this->attributes, []); - $response = $validator->isValid(DatabaseQuery::parse('equal("name", "Iron Man")')); + $response = $validator->isValid([Query::parse('equal("name", "Iron Man")')]); $this->assertEquals(false, $response); - $this->assertEquals('Attribute not found in schema: name', $validator->getDescription()); + $this->assertEquals('Query not valid: Attribute not found in schema: name', $validator->getDescription()); - $response = $validator->isValid(DatabaseQuery::parse('orderAsc("name")')); + $response = $validator->isValid([Query::parse('orderAsc("name")')]); $this->assertEquals(false, $response); - $this->assertEquals('Attribute not found in schema: name', $validator->getDescription()); + $this->assertEquals('Query not valid: Attribute not found in schema: name', $validator->getDescription()); } public function testAttributeWrongType(): void { - $validator = new Query($this->schema); + $validator = new \Utopia\Database\Validator\Document($this->attributes, []); - $response = $validator->isValid(DatabaseQuery::parse('equal("title", 1776)')); + $response = $validator->isValid([Query::parse('equal("title", 1776)')]); $this->assertEquals(false, $response); - $this->assertEquals('Query type does not match expected: string', $validator->getDescription()); + $this->assertEquals('Query not valid: Query type does not match expected: string', $validator->getDescription()); } - public function testMethodWrongType(): void - { - $validator = new Query($this->schema); - - $response = $validator->isValid(DatabaseQuery::parse('contains("title", "Iron")')); - - $this->assertEquals(false, $response); - $this->assertEquals('Query method only supported on array attributes: contains', $validator->getDescription()); - } +// public function testMethodWrongType(): void +// { +// $validator = new \Utopia\Database\Validator\Document($this->attributes, []); +// +// $response = $validator->isValid([Query::parse('contains("title", "Iron")')]); +// +// $this->assertEquals(false, $response); +// $this->assertEquals('Query method only supported on array attributes: contains', $validator->getDescription()); +// } public function testQueryDate(): void { - $validator = new Query($this->schema); - $response = $validator->isValid(DatabaseQuery::parse('greaterThan("birthDay", "1960-01-01 10:10:10")')); + $validator = new \Utopia\Database\Validator\Document($this->attributes, []); + $response = $validator->isValid([Query::parse('greaterThan("birthDay", "1960-01-01 10:10:10")')]); $this->assertEquals(true, $response); } public function testQueryLimit(): void { - $validator = new Query($this->schema); + $validator = new \Utopia\Database\Validator\Document($this->attributes, []); - $response = $validator->isValid(DatabaseQuery::parse('limit(25)')); + $response = $validator->isValid([Query::parse('limit(25)')]); $this->assertEquals(true, $response); - $response = $validator->isValid(DatabaseQuery::parse('limit()')); + $response = $validator->isValid([Query::parse('limit()')]); $this->assertEquals(false, $response); - $response = $validator->isValid(DatabaseQuery::parse('limit(-1)')); + $response = $validator->isValid([Query::parse('limit(-1)')]); $this->assertEquals(false, $response); - $response = $validator->isValid(DatabaseQuery::parse('limit(10000)')); + $response = $validator->isValid([Query::parse('limit("aaa")')]); $this->assertEquals(false, $response); } public function testQueryOffset(): void { - $validator = new Query($this->schema); + $validator = new \Utopia\Database\Validator\Document($this->attributes, []); - $response = $validator->isValid(DatabaseQuery::parse('offset(25)')); + $response = $validator->isValid([Query::parse('offset(25)')]); $this->assertEquals(true, $response); - $response = $validator->isValid(DatabaseQuery::parse('offset()')); + $response = $validator->isValid([Query::parse('offset()')]); $this->assertEquals(false, $response); - $response = $validator->isValid(DatabaseQuery::parse('offset(-1)')); + $response = $validator->isValid([Query::parse('offset(-1)')]); $this->assertEquals(false, $response); - $response = $validator->isValid(DatabaseQuery::parse('offset(10000)')); + $response = $validator->isValid([Query::parse('offset("aaa")')]); $this->assertEquals(false, $response); } public function testQueryOrder(): void { - $validator = new Query($this->schema); + $validator = new \Utopia\Database\Validator\Document($this->attributes, []); - $response = $validator->isValid(DatabaseQuery::parse('orderAsc("title")')); + $response = $validator->isValid([Query::parse('orderAsc("title")')]); $this->assertEquals(true, $response); - $response = $validator->isValid(DatabaseQuery::parse('orderAsc("")')); + $response = $validator->isValid([Query::parse('orderAsc("")')]); $this->assertEquals(true, $response); - $response = $validator->isValid(DatabaseQuery::parse('orderAsc()')); + $response = $validator->isValid([Query::parse('orderAsc()')]); $this->assertEquals(true, $response); - $response = $validator->isValid(DatabaseQuery::parse('orderAsc("doesNotExist")')); + $response = $validator->isValid([Query::parse('orderAsc("doesNotExist")')]); $this->assertEquals(false, $response); } public function testQueryCursor(): void { - $validator = new Query($this->schema); + $validator = new \Utopia\Database\Validator\Document($this->attributes, []); - $response = $validator->isValid(DatabaseQuery::parse('cursorAfter("asdf")')); + $response = $validator->isValid([Query::parse('cursorAfter("asdf")')]); $this->assertEquals(true, $response); - $response = $validator->isValid(DatabaseQuery::parse('cursorAfter()')); + $response = $validator->isValid([Query::parse('cursorAfter()')]); $this->assertEquals(false, $response); } } From 6043d50343e13cf581dea688ba07660ef3dd819e Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 24 Apr 2023 18:18:08 +0300 Subject: [PATCH 05/75] empty values && validate find queries --- src/Database/Database.php | 10 +- src/Database/Validator/Document.php | 1 + src/Database/Validator/Queries.php | 12 ++- src/Database/Validator/Query/Cursor.php | 13 ++- src/Database/Validator/Query/Filter.php | 33 +++--- src/Database/Validator/Query/Order.php | 3 - tests/Database/Adapter/MariaDBTest.php | 130 +++++++++++------------ tests/Database/Base.php | 83 +++++++++++++++ tests/Database/Validator/QueriesTest.php | 6 +- tests/Database/Validator/QueryTest.php | 2 +- 10 files changed, 193 insertions(+), 100 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 989bd6c17..90e8eb410 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -15,6 +15,7 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\Authorization; +use Utopia\Database\Validator\Document as DocumentValidator; use Utopia\Database\Validator\Index as IndexValidator; use Utopia\Database\Validator\Structure; @@ -3557,6 +3558,13 @@ public function find(string $collection, array $queries = [], ?int $timeout = nu } $collection = $this->silent(fn () => $this->getCollection($collection)); + $attributes = $collection->getAttribute('attributes', []); + $indexes = $collection->getAttribute('indexes', []); + +// $validator = new DocumentValidator($attributes, $indexes); +// if(!$validator->isValid($queries)){ +// throw new Exception($validator->getDescription()); +// } $relationships = \array_filter( $collection->getAttribute('attributes', []), @@ -3644,8 +3652,6 @@ public function find(string $collection, array $queries = [], ?int $timeout = nu $timeout ); - $attributes = $collection->getAttribute('attributes', []); - $relationships = $this->resolveRelationships ? \array_filter($attributes, function (Document $attribute) { return $attribute->getAttribute('type') === self::VAR_RELATIONSHIP; }) : []; diff --git a/src/Database/Validator/Document.php b/src/Database/Validator/Document.php index 5a9c088c5..26d9fff8d 100644 --- a/src/Database/Validator/Document.php +++ b/src/Database/Validator/Document.php @@ -18,6 +18,7 @@ class Document extends IndexedQueries * Expression constructor * * @param UtopiaDocument[] $attributes + * @param UtopiaDocument[] $indexes * @throws Exception */ public function __construct(array $attributes, array $indexes) diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index 1e25476a4..b67e272d5 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -87,24 +87,26 @@ public function isValid($value): bool default => '', }; + $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; } - return true; + $methodIsValid = true; } - $this->message = 'Query method invalid: ' . $method; - return false; + if (!$methodIsValid) { + $this->message = 'Query method not valid: ' . $method; + return false; + } } - return false; + return true; } /** diff --git a/src/Database/Validator/Query/Cursor.php b/src/Database/Validator/Query/Cursor.php index 824cd61ad..57efee8e8 100644 --- a/src/Database/Validator/Query/Cursor.php +++ b/src/Database/Validator/Query/Cursor.php @@ -2,6 +2,7 @@ namespace Utopia\Database\Validator\Query; +use Utopia\Database\Document; use Utopia\Database\Query; use Utopia\Database\Validator\UID; @@ -18,13 +19,17 @@ class Cursor extends Base * * @return bool */ - public function isValid($query): bool + public function isValid($value): bool { - // Validate method - $method = $query->getMethod(); + $method = $value->getMethod(); if ($method === Query::TYPE_CURSORAFTER || $method === Query::TYPE_CURSORBEFORE) { - $cursor = $query->getValue(); + $cursor = $value->getValue(); + + if($cursor instanceof Document){ + $cursor = $cursor->getId(); + } + $validator = new UID(); if ($validator->isValid($cursor)) { return true; diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index 62d82b8b8..d576f49ed 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -7,16 +7,8 @@ class Filter extends Base { - /** - * @var string - */ protected string $message = 'Invalid query'; - - /** - * @var array - */ - protected $schema = []; - + protected array $schema = []; private int $maxValuesCount; /** @@ -40,11 +32,12 @@ protected function isValidAttribute($attribute): bool // Utopia will validate each nested level during the recursive calls. $attribute = \explode('.', $attribute)[0]; - // TODO: Remove this when nested queries are supported - if (isset($this->schema[$attribute])) { - $this->message = 'Cannot query nested attribute on: ' . $attribute; - return false; - } + // TODO: This must be taken care of in appwrite now... + +// if (isset($this->schema[$attribute])) { +// $this->message = 'Cannot query nested attribute on: ' . $attribute; +// return false; +// } } // Search for attribute in schema @@ -109,7 +102,6 @@ public function isValid($query): bool { $method = $query->getMethod(); $attribute = $query->getAttribute(); - switch ($method) { case Query::TYPE_EQUAL: case Query::TYPE_NOTEQUAL: @@ -121,12 +113,19 @@ public function isValid($query): bool case Query::TYPE_STARTS_WITH: case Query::TYPE_ENDS_WITH: case Query::TYPE_BETWEEN: - case Query::TYPE_IS_NULL: - case Query::TYPE_IS_NOT_NULL: case Query::TYPE_CONTAINS: // todo: What to do about unsupported operators? $values = $query->getValues(); + if(empty($values) || (isset($values[0]) && empty($values[0]))){ + $this->message = $method . ' queries require at least one value.'; + return false; + } + return $this->isValidAttributeAndValues($attribute, $values); + case Query::TYPE_IS_NULL: + case Query::TYPE_IS_NOT_NULL: + return $this->isValidAttributeAndValues($attribute, $query->getValues()); + default: return false; } diff --git a/src/Database/Validator/Query/Order.php b/src/Database/Validator/Query/Order.php index db775a05a..64c0d3d17 100644 --- a/src/Database/Validator/Query/Order.php +++ b/src/Database/Validator/Query/Order.php @@ -6,9 +6,6 @@ class Order extends Base { - /** - * @var array - */ protected array $schema = []; /** diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php index f14b2882f..9cc3fc781 100644 --- a/tests/Database/Adapter/MariaDBTest.php +++ b/tests/Database/Adapter/MariaDBTest.php @@ -1,66 +1,66 @@ connect('redis', 6379); -// $redis->flushAll(); -// $cache = new Cache(new RedisAdapter($redis)); -// -// $database = new Database(new MariaDB($pdo), $cache); -// $database->setDefaultDatabase('utopiaTests'); -// $database->setNamespace('myapp_'.uniqid()); -// -// if ($database->exists('utopiaTests')) { -// $database->delete('utopiaTests'); -// } -// -// $database->create(); -// -// return self::$database = $database; -// } -// -// public static function killDatabase(): void -// { -// self::$database = null; -// } -//} + +namespace Utopia\Tests\Adapter; + +use PDO; +use Redis; +use Utopia\Database\Database; +use Utopia\Database\Adapter\MariaDB; +use Utopia\Cache\Cache; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Tests\Base; + +class MariaDBTest extends Base +{ + public static ?Database $database = null; + + // TODO@kodumbeats hacky way to identify adapters for tests + // Remove once all methods are implemented + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "mariadb"; + } + + /** + * @return Database + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $dbHost = 'mariadb'; + $dbPort = '3306'; + $dbUser = 'root'; + $dbPass = 'password'; + + $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MariaDB::getPDOAttributes()); + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + $cache = new Cache(new RedisAdapter($redis)); + + $database = new Database(new MariaDB($pdo), $cache); + $database->setDefaultDatabase('utopiaTests'); + $database->setNamespace('myapp_'.uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + + $database->create(); + + return self::$database = $database; + } + + public static function killDatabase(): void + { + self::$database = null; + } +} diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 2478c5577..90ca51965 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -10048,6 +10048,89 @@ public function testEvents(): void $database->delete('hellodb'); }); } +// +// public function testEmptyOperatorValues(): void +// { +// try { +// static::getDatabase()->findOne('documents', [ +// Query::equal('string', []), +// ]); +// $this->fail('Failed to throw exception'); +// } catch (Exception $e) { +// $this->assertInstanceOf(Exception::class, $e); +// $this->assertEquals('Query not valid: equal queries require at least one value.', $e->getMessage()); +// } +// +// try { +// static::getDatabase()->findOne('documents', [ +// Query::search('string', null), +// ]); +// $this->fail('Failed to throw exception'); +// } catch (Exception $e) { +// $this->assertInstanceOf(Exception::class, $e); +// $this->assertEquals('Query not valid: Query type does not match expected: string', $e->getMessage()); +// } +// +// try { +// static::getDatabase()->findOne('documents', [ +// Query::notEqual('string', []), +// ]); +// $this->fail('Failed to throw exception'); +// } catch (Exception $e) { +// $this->assertInstanceOf(Exception::class, $e); +// $this->assertEquals('Query not valid: notEqual queries require at least one value.', $e->getMessage()); +// } +// +// try { +// static::getDatabase()->findOne('documents', [ +// Query::lessThan('string', []), +// ]); +// $this->fail('Failed to throw exception'); +// } catch (Exception $e) { +// $this->assertInstanceOf(Exception::class, $e); +// $this->assertEquals('Query not valid: lessThan queries require at least one value.', $e->getMessage()); +// } +// +// try { +// static::getDatabase()->findOne('documents', [ +// Query::lessThanEqual('string', []), +// ]); +// $this->fail('Failed to throw exception'); +// } catch (Exception $e) { +// $this->assertInstanceOf(Exception::class, $e); +// $this->assertEquals('Query not valid: lessThanEqual queries require at least one value.', $e->getMessage()); +// } +// +// try { +// static::getDatabase()->findOne('documents', [ +// Query::greaterThan('string', []), +// ]); +// $this->fail('Failed to throw exception'); +// } catch (Exception $e) { +// $this->assertInstanceOf(Exception::class, $e); +// $this->assertEquals('Query not valid: greaterThan queries require at least one value.', $e->getMessage()); +// } +// +// try { +// static::getDatabase()->findOne('documents', [ +// Query::greaterThanEqual('string', []), +// ]); +// $this->fail('Failed to throw exception'); +// } catch (Exception $e) { +// $this->assertInstanceOf(Exception::class, $e); +// $this->assertEquals('Query not valid: greaterThanEqual queries require at least one value.', $e->getMessage()); +// } +// +// try { +// static::getDatabase()->findOne('documents', [ +// Query::contains('string', []), +// ]); +// $this->fail('Failed to throw exception'); +// } catch (Exception $e) { +// $this->assertInstanceOf(Exception::class, $e); +// $this->assertEquals('Query not valid: contains queries require at least one value.', $e->getMessage()); +// } +// } public function testLast(): void { diff --git a/tests/Database/Validator/QueriesTest.php b/tests/Database/Validator/QueriesTest.php index b8c5a6145..c4e2c93f8 100644 --- a/tests/Database/Validator/QueriesTest.php +++ b/tests/Database/Validator/QueriesTest.php @@ -156,9 +156,9 @@ public function testInvalidQueries(): void $this->assertEquals(false, $validator->isValid($queries)); $this->assertEquals('Query not valid: Invalid limit: Value must be a valid range between 1 and 9,223,372,036,854,775,808', $validator->getDescription()); -// $queries = ['equal("title", [])']; // empty array -// $this->assertEquals(false, $validator->isValid($queries)); -// $this->assertEquals('We want this to fail!', $validator->getDescription()); + $queries = ['equal("title", [])']; // empty array + $this->assertEquals(false, $validator->isValid($queries)); + $this->assertEquals('Query not valid: equal queries require at least one value.', $validator->getDescription()); } diff --git a/tests/Database/Validator/QueryTest.php b/tests/Database/Validator/QueryTest.php index 98e6fe515..017c76de4 100644 --- a/tests/Database/Validator/QueryTest.php +++ b/tests/Database/Validator/QueryTest.php @@ -138,7 +138,7 @@ public function testInvalidMethod(): void $validator = new \Utopia\Database\Validator\Document($this->attributes, []); $this->assertEquals(false, $validator->isValid([Query::parse('eqqual("title", "Iron Man")')])); - $this->assertEquals('Query method invalid: eqqual', $validator->getDescription()); + $this->assertEquals('Query method not valid: eqqual', $validator->getDescription()); } public function testAttributeNotFound(): void From 8921aa8639146952516a86bc3cd82fdab254589d Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 24 Apr 2023 19:06:29 +0300 Subject: [PATCH 06/75] phpstan fixes --- src/Database/Validator/IndexedQueries.php | 2 +- src/Database/Validator/Queries.php | 6 +++--- src/Database/Validator/Query/Cursor.php | 2 +- src/Database/Validator/Query/Filter.php | 22 ++++++++++++++++++---- src/Database/Validator/Query/Order.php | 15 +++++++++++---- src/Database/Validator/Query/Select.php | 7 +++++-- src/Database/Validator/QueryOrg.php | 1 + tests/Database/Adapter/MongoDBTest.php | 1 + tests/Database/Adapter/MySQLTest.php | 1 + tests/Database/Adapter/PostgresTest.php | 1 + tests/Database/Adapter/SQLiteTest.php | 1 + tests/Database/Validator/QueriesTest.php | 3 --- tests/Database/Validator/QueryTest.php | 7 ------- 13 files changed, 44 insertions(+), 25 deletions(-) diff --git a/src/Database/Validator/IndexedQueries.php b/src/Database/Validator/IndexedQueries.php index 90f4da189..99fdce0c0 100644 --- a/src/Database/Validator/IndexedQueries.php +++ b/src/Database/Validator/IndexedQueries.php @@ -49,7 +49,7 @@ public function __construct(array $attributes = [], array $indexes = [], Base .. 'attributes' => ['$updatedAt'] ]); - foreach ($indexes ?? [] as $index) { + foreach ($indexes as $index) { $this->indexes[] = $index; } diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index b67e272d5..e0a1493c3 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -2,6 +2,7 @@ namespace Utopia\Database\Validator; +use Utopia\Database\Document; use Utopia\Database\Validator\Query\Base; use Utopia\Validator; use Utopia\Database\Query; @@ -41,12 +42,12 @@ public function getDescription(): string } /** - * @param array $value + * @param mixed $value * @return bool */ public function isValid($value): bool { - if(!is_array($value)){ + if (!is_array($value)) { $this->message = "Queries must be an array"; return false; } @@ -55,7 +56,6 @@ public function isValid($value): bool if (!$query instanceof Query) { try { $query = Query::parse($query); - } catch (\Throwable) { $this->message = "Invalid query: {$query}"; return false; diff --git a/src/Database/Validator/Query/Cursor.php b/src/Database/Validator/Query/Cursor.php index 57efee8e8..0e19fd7cf 100644 --- a/src/Database/Validator/Query/Cursor.php +++ b/src/Database/Validator/Query/Cursor.php @@ -26,7 +26,7 @@ public function isValid($value): bool if ($method === Query::TYPE_CURSORAFTER || $method === Query::TYPE_CURSORBEFORE) { $cursor = $value->getValue(); - if($cursor instanceof Document){ + if ($cursor instanceof Document) { $cursor = $cursor->getId(); } diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index d576f49ed..cf2802001 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -3,17 +3,22 @@ namespace Utopia\Database\Validator\Query; use Utopia\Database\Database; +use Utopia\Database\Document; use Utopia\Database\Query; class Filter extends Base { protected string $message = 'Invalid query'; + + /** + * @var array + */ protected array $schema = []; + private int $maxValuesCount; /** - * Query constructor - * + * @param array $attributes * @param int $maxValuesCount */ public function __construct(array $attributes = [], int $maxValuesCount = 100) @@ -25,7 +30,11 @@ public function __construct(array $attributes = [], int $maxValuesCount = 100) $this->maxValuesCount = $maxValuesCount; } - protected function isValidAttribute($attribute): bool + /** + * @param string $attribute + * @return bool + */ + protected function isValidAttribute(string $attribute): bool { if (\str_contains($attribute, '.')) { // For relationships, just validate the top level. @@ -49,6 +58,11 @@ protected function isValidAttribute($attribute): bool return true; } + /** + * @param string $attribute + * @param array $values + * @return bool + */ protected function isValidAttributeAndValues(string $attribute, array $values): bool { if (!$this->isValidAttribute($attribute)) { @@ -115,7 +129,7 @@ public function isValid($query): bool case Query::TYPE_BETWEEN: case Query::TYPE_CONTAINS: // todo: What to do about unsupported operators? $values = $query->getValues(); - if(empty($values) || (isset($values[0]) && empty($values[0]))){ + if (empty($values) || (isset($values[0]) && empty($values[0]))) { $this->message = $method . ' queries require at least one value.'; return false; } diff --git a/src/Database/Validator/Query/Order.php b/src/Database/Validator/Query/Order.php index 64c0d3d17..e22374632 100644 --- a/src/Database/Validator/Query/Order.php +++ b/src/Database/Validator/Query/Order.php @@ -2,14 +2,18 @@ namespace Utopia\Database\Validator\Query; +use Utopia\Database\Document; use Utopia\Database\Query; class Order extends Base { + /** + * @var array + */ protected array $schema = []; /** - * Query constructor + * @param array $attributes */ public function __construct(array $attributes = []) { @@ -18,7 +22,11 @@ public function __construct(array $attributes = []) } } - protected function isValidAttribute($attribute): bool + /** + * @param string $attribute + * @return bool + */ + protected function isValidAttribute(string $attribute): bool { // Search for attribute in schema if (!isset($this->schema[$attribute])) { @@ -36,8 +44,7 @@ protected function isValidAttribute($attribute): bool * * Otherwise, returns false * - * @param Query $value - * + * @param Query $query * @return bool */ public function isValid($query): bool diff --git a/src/Database/Validator/Query/Select.php b/src/Database/Validator/Query/Select.php index 591c53889..44ce03cc3 100644 --- a/src/Database/Validator/Query/Select.php +++ b/src/Database/Validator/Query/Select.php @@ -2,15 +2,18 @@ namespace Utopia\Database\Validator\Query; +use Utopia\Database\Document; use Utopia\Database\Query; class Select extends Base { + /** + * @var array + */ protected array $schema = []; /** - * Query constructor - * + * @param array $attributes */ public function __construct(array $attributes = []) { diff --git a/src/Database/Validator/QueryOrg.php b/src/Database/Validator/QueryOrg.php index 416aee434..176c96b14 100644 --- a/src/Database/Validator/QueryOrg.php +++ b/src/Database/Validator/QueryOrg.php @@ -1,4 +1,5 @@ assertEquals(false, $validator->isValid($queries)); $this->assertEquals('Query not valid: equal queries require at least one value.', $validator->getDescription()); - } - - } diff --git a/tests/Database/Validator/QueryTest.php b/tests/Database/Validator/QueryTest.php index 017c76de4..2232c882c 100644 --- a/tests/Database/Validator/QueryTest.php +++ b/tests/Database/Validator/QueryTest.php @@ -7,13 +7,6 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; -use Utopia\Database\Validator\Queries; -use Utopia\Database\Validator\Query\Cursor; -use Utopia\Database\Validator\Query\Filter; -use Utopia\Database\Validator\Query\Limit; -use Utopia\Database\Validator\Query\Offset; -use Utopia\Database\Validator\Query\Order; -use Utopia\Database\Validator\Query\Select; class QueryTest extends TestCase { From 67b4f097bc6315668adc6ba477f36d8e4e43204c Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 24 Apr 2023 20:25:10 +0300 Subject: [PATCH 07/75] contains test remove --- src/Database/Validator/Query/Filter.php | 5 + tests/Database/Adapter/MariaDBTest.php | 130 ++++++++++++------------ tests/Database/Validator/QueryTest.php | 14 +-- 3 files changed, 73 insertions(+), 76 deletions(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index cf2802001..d2755a660 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -99,6 +99,11 @@ protected function isValidAttributeAndValues(string $attribute, array $values): } } + if($attributeSchema['array'] && !is_array($values)){ + $this->message = 'Query method only supported on array'; + return false; + } + return true; } diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php index 9cc3fc781..f14b2882f 100644 --- a/tests/Database/Adapter/MariaDBTest.php +++ b/tests/Database/Adapter/MariaDBTest.php @@ -1,66 +1,66 @@ connect('redis', 6379); - $redis->flushAll(); - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new MariaDB($pdo), $cache); - $database->setDefaultDatabase('utopiaTests'); - $database->setNamespace('myapp_'.uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } - - public static function killDatabase(): void - { - self::$database = null; - } -} +// +//namespace Utopia\Tests\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Database\Database; +//use Utopia\Database\Adapter\MariaDB; +//use Utopia\Cache\Cache; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Tests\Base; +// +//class MariaDBTest extends Base +//{ +// public static ?Database $database = null; +// +// // TODO@kodumbeats hacky way to identify adapters for tests +// // Remove once all methods are implemented +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "mariadb"; +// } +// +// /** +// * @return Database +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $dbHost = 'mariadb'; +// $dbPort = '3306'; +// $dbUser = 'root'; +// $dbPass = 'password'; +// +// $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MariaDB::getPDOAttributes()); +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new MariaDB($pdo), $cache); +// $database->setDefaultDatabase('utopiaTests'); +// $database->setNamespace('myapp_'.uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +// +// public static function killDatabase(): void +// { +// self::$database = null; +// } +//} diff --git a/tests/Database/Validator/QueryTest.php b/tests/Database/Validator/QueryTest.php index 2232c882c..fdbbd6a1e 100644 --- a/tests/Database/Validator/QueryTest.php +++ b/tests/Database/Validator/QueryTest.php @@ -107,12 +107,14 @@ public function testQuery(): void $validator = new \Utopia\Database\Validator\Document($this->attributes, []); $this->assertEquals(true, $validator->isValid([Query::parse('equal("$id", ["Iron Man", "Ant Man"])')])); + $this->assertEquals(true, $validator->isValid([Query::parse('equal("$id", "Iron Man")')])); $this->assertEquals(true, $validator->isValid([Query::parse('notEqual("title", ["Iron Man", "Ant Man"])')])); $this->assertEquals(true, $validator->isValid([Query::parse('equal("description", "Best movie ever")')])); $this->assertEquals(true, $validator->isValid([Query::parse('greaterThan("rating", 4)')])); $this->assertEquals(true, $validator->isValid([Query::parse('lessThan("price", 6.50)')])); $this->assertEquals(true, $validator->isValid([Query::parse('lessThanEqual("price", 6)')])); - $this->assertEquals(true, $validator->isValid([Query::parse('contains("tags", "action")')])); + $this->assertEquals(true, $validator->isValid([Query::parse('contains("tags", ["action1", "action2"])')])); + $this->assertEquals(true, $validator->isValid([Query::parse('contains("tags", "action1")')])); $this->assertEquals(true, $validator->isValid([Query::parse('cursorAfter("docId")')])); $this->assertEquals(true, $validator->isValid([Query::parse('cursorBefore("docId")')])); $this->assertEquals(true, $validator->isValid([Query::parse('orderAsc("title")')])); @@ -159,16 +161,6 @@ public function testAttributeWrongType(): void $this->assertEquals('Query not valid: Query type does not match expected: string', $validator->getDescription()); } -// public function testMethodWrongType(): void -// { -// $validator = new \Utopia\Database\Validator\Document($this->attributes, []); -// -// $response = $validator->isValid([Query::parse('contains("title", "Iron")')]); -// -// $this->assertEquals(false, $response); -// $this->assertEquals('Query method only supported on array attributes: contains', $validator->getDescription()); -// } - public function testQueryDate(): void { $validator = new \Utopia\Database\Validator\Document($this->attributes, []); From 00f473bff5f70a86be11519bdb0eca61af740f2a Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 25 Apr 2023 12:59:20 +0300 Subject: [PATCH 08/75] comment queries org --- src/Database/Validator/QueriesOrg.php | 442 +++++++++++++------------- 1 file changed, 221 insertions(+), 221 deletions(-) diff --git a/src/Database/Validator/QueriesOrg.php b/src/Database/Validator/QueriesOrg.php index dad386dff..65c31ffb0 100644 --- a/src/Database/Validator/QueriesOrg.php +++ b/src/Database/Validator/QueriesOrg.php @@ -1,222 +1,222 @@ - */ - protected array $attributes = []; - - /** - * @var array - */ - protected array $indexes = []; - protected bool $strict; - - /** - * Queries constructor - * - * @param QueryValidator $validator used to validate each query - * @param array|null $attributes allowed attributes to be queried - * @param array|null $indexes available for strict query matching - * @param bool $strict - * @throws Exception - */ - public function __construct(QueryValidator $validator, ?array $attributes, ?array $indexes, bool $strict = true) - { - $this->validator = $validator; - $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; - } - - $this->strict = $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 - * - * In addition, if $strict is true, this returns false if: - * 1. there is no index with an exact match of the filters - * 2. 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 - { - $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; - } - - if (!$this->strict) { - return true; - } - - $grouped = Query::groupByType($queries); - /** @var array $filters */ - $filters = $grouped['filters']; - /** @var array $orderAttributes */ - $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; - } - - /** - * 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; - } - - /** - * Is Strict - * - * Returns true if strict validation is set - * - * @return bool - */ - public function isStrict(): bool - { - return $this->strict; - } - - /** - * 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; - } -} +// +//namespace Utopia\Database\Validator; +// +//use Exception; +//use Utopia\Database\Database; +//use Utopia\Database\Document; +//use Utopia\Database\Query; +//use Utopia\Database\Validator\Query as QueryValidator; +//use Utopia\Validator; +// +//class QueriesOrg extends Validator +//{ +// protected string $message = 'Invalid queries'; +// +// protected QueryValidator $validator; +// +// /** +// * @var array +// */ +// protected array $attributes = []; +// +// /** +// * @var array +// */ +// protected array $indexes = []; +// protected bool $strict; +// +// /** +// * Queries constructor +// * +// * @param QueryValidator $validator used to validate each query +// * @param array|null $attributes allowed attributes to be queried +// * @param array|null $indexes available for strict query matching +// * @param bool $strict +// * @throws Exception +// */ +// public function __construct(QueryValidator $validator, ?array $attributes, ?array $indexes, bool $strict = true) +// { +// $this->validator = $validator; +// $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; +// } +// +// $this->strict = $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 +// * +// * In addition, if $strict is true, this returns false if: +// * 1. there is no index with an exact match of the filters +// * 2. 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 +// { +// $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; +// } +// +// if (!$this->strict) { +// return true; +// } +// +// $grouped = Query::groupByType($queries); +// /** @var array $filters */ +// $filters = $grouped['filters']; +// /** @var array $orderAttributes */ +// $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; +// } +// +// /** +// * 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; +// } +// +// /** +// * Is Strict +// * +// * Returns true if strict validation is set +// * +// * @return bool +// */ +// public function isStrict(): bool +// { +// return $this->strict; +// } +// +// /** +// * 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; +// } +//} From 27e202362615f64da3408241e2a2bf522d6cca50 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 25 Apr 2023 14:10:00 +0300 Subject: [PATCH 09/75] Query test from appwrite --- .../Database/Validator/IndexedQueriesTest.php | 121 ++++++++++++++++++ tests/Database/Validator/Query/CursorTest.php | 28 ++++ tests/Database/Validator/Query/FilterTest.php | 54 ++++++++ tests/Database/Validator/Query/LimitTest.php | 24 ++++ tests/Database/Validator/Query/OffsetTest.php | 28 ++++ tests/Database/Validator/Query/OrderTest.php | 42 ++++++ tests/Database/Validator/Query/SelectTest.php | 31 +++++ 7 files changed, 328 insertions(+) create mode 100644 tests/Database/Validator/IndexedQueriesTest.php create mode 100644 tests/Database/Validator/Query/CursorTest.php create mode 100644 tests/Database/Validator/Query/FilterTest.php create mode 100644 tests/Database/Validator/Query/LimitTest.php create mode 100644 tests/Database/Validator/Query/OffsetTest.php create mode 100644 tests/Database/Validator/Query/OrderTest.php create mode 100644 tests/Database/Validator/Query/SelectTest.php diff --git a/tests/Database/Validator/IndexedQueriesTest.php b/tests/Database/Validator/IndexedQueriesTest.php new file mode 100644 index 000000000..f6c59131b --- /dev/null +++ b/tests/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/Database/Validator/Query/CursorTest.php b/tests/Database/Validator/Query/CursorTest.php new file mode 100644 index 000000000..5f52f516d --- /dev/null +++ b/tests/Database/Validator/Query/CursorTest.php @@ -0,0 +1,28 @@ +assertEquals($validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), true, $validator->getDescription()); + + // Test for Failure + $this->assertEquals($validator->isValid(Query::limit(-1)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::limit(101)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::offset(-1)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::offset(5001)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::equal('attr', ['v'])), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::orderAsc('attr')), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::orderDesc('attr')), false, $validator->getDescription()); + } +} diff --git a/tests/Database/Validator/Query/FilterTest.php b/tests/Database/Validator/Query/FilterTest.php new file mode 100644 index 000000000..0c61b8f75 --- /dev/null +++ b/tests/Database/Validator/Query/FilterTest.php @@ -0,0 +1,54 @@ +validator = new Filter( + attributes: [ + new Document([ + 'key' => 'attr', + 'type' => Database::VAR_STRING, + 'array' => false, + ]), + ], + ); + // Test for Success + $this->assertEquals($this->validator->isValid(Query::between('attr', '1975-12-06', '2050-12-06')), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::isNotNull('attr')), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::isNull('attr')), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::startsWith('attr', 'super')), true, $this->validator->getDescription()); + $this->assertEquals($this->validator->isValid(Query::endsWith('attr', 'man')), true, $this->validator->getDescription()); + + // Test for Failure + $this->assertEquals($this->validator->isValid(Query::select(['attr'])), false, $this->validator->getDescription()); + $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/Database/Validator/Query/LimitTest.php b/tests/Database/Validator/Query/LimitTest.php new file mode 100644 index 000000000..636e07fcc --- /dev/null +++ b/tests/Database/Validator/Query/LimitTest.php @@ -0,0 +1,24 @@ +assertEquals($validator->isValid(Query::limit(1)), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::limit(100)), true, $validator->getDescription()); + + // Test for Failure + $this->assertEquals($validator->isValid(Query::limit(0)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::limit(-1)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::limit(101)), false, $validator->getDescription()); + } +} diff --git a/tests/Database/Validator/Query/OffsetTest.php b/tests/Database/Validator/Query/OffsetTest.php new file mode 100644 index 000000000..4f1ef7b5d --- /dev/null +++ b/tests/Database/Validator/Query/OffsetTest.php @@ -0,0 +1,28 @@ +assertEquals($validator->isValid(Query::offset(1)), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::offset(0)), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::offset(5000)), true, $validator->getDescription()); + + // Test for Failure + $this->assertEquals($validator->isValid(Query::offset(-1)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::offset(5001)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::equal('attr', ['v'])), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::orderAsc('attr')), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::orderDesc('attr')), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::limit(100)), false, $validator->getDescription()); + } +} diff --git a/tests/Database/Validator/Query/OrderTest.php b/tests/Database/Validator/Query/OrderTest.php new file mode 100644 index 000000000..1224fa5d0 --- /dev/null +++ b/tests/Database/Validator/Query/OrderTest.php @@ -0,0 +1,42 @@ + 'attr', + 'type' => Database::VAR_STRING, + 'array' => false, + ]), + ], + ); + + // Test for Success + $this->assertEquals($validator->isValid(Query::orderAsc('attr')), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::orderAsc('')), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::orderDesc('attr')), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::orderDesc('')), true, $validator->getDescription()); + + // Test for Failure + $this->assertEquals($validator->isValid(Query::limit(-1)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::limit(101)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::offset(-1)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::offset(5001)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::equal('attr', ['v'])), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::equal('dne', ['v'])), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::equal('', ['v'])), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::orderDesc('dne')), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::orderAsc('dne')), false, $validator->getDescription()); + } +} diff --git a/tests/Database/Validator/Query/SelectTest.php b/tests/Database/Validator/Query/SelectTest.php new file mode 100644 index 000000000..58520a357 --- /dev/null +++ b/tests/Database/Validator/Query/SelectTest.php @@ -0,0 +1,31 @@ + 'attr', + 'type' => Database::VAR_STRING, + 'array' => false, + ]), + ], + ); + + // Test for Success + $this->assertEquals($validator->isValid(Query::select(['*', 'attr'])), true, $validator->getDescription()); + + // Test for Failure + $this->assertEquals($validator->isValid(Query::limit(1)), false, $validator->getDescription()); + } +} From 94bca644f3ff418e6742e89fcf571c2c342adcd6 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 27 Apr 2023 09:57:58 +0300 Subject: [PATCH 10/75] name changes --- composer.lock | 22 +++++----- src/Database/Database.php | 10 ++--- src/Database/Validator/DocumentValidator.php | 41 +++++++++++++++++++ .../Validator/{Document.php => Documents.php} | 2 +- tests/Database/Validator/QueriesTest.php | 6 +-- tests/Database/Validator/QueryTest.php | 18 ++++---- 6 files changed, 70 insertions(+), 29 deletions(-) create mode 100644 src/Database/Validator/DocumentValidator.php rename src/Database/Validator/{Document.php => Documents.php} (97%) diff --git a/composer.lock b/composer.lock index e34166784..7b1ac6551 100644 --- a/composer.lock +++ b/composer.lock @@ -906,16 +906,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.13", + "version": "1.10.14", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "f07bf8c6980b81bf9e49d44bd0caf2e737614a70" + "reference": "d232901b09e67538e5c86a724be841bea5768a7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f07bf8c6980b81bf9e49d44bd0caf2e737614a70", - "reference": "f07bf8c6980b81bf9e49d44bd0caf2e737614a70", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d232901b09e67538e5c86a724be841bea5768a7c", + "reference": "d232901b09e67538e5c86a724be841bea5768a7c", "shasum": "" }, "require": { @@ -964,7 +964,7 @@ "type": "tidelift" } ], - "time": "2023-04-12T19:29:52+00:00" + "time": "2023-04-19T13:47:27+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1286,16 +1286,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.6", + "version": "9.6.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115" + "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b65d59a059d3004a040c16a82e07bbdf6cfdd115", - "reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", + "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", "shasum": "" }, "require": { @@ -1369,7 +1369,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.6" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7" }, "funding": [ { @@ -1385,7 +1385,7 @@ "type": "tidelift" } ], - "time": "2023-03-27T11:43:46+00:00" + "time": "2023-04-14T08:58:40+00:00" }, { "name": "psr/container", diff --git a/src/Database/Database.php b/src/Database/Database.php index 90e8eb410..25dc08e8a 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -15,7 +15,7 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\Authorization; -use Utopia\Database\Validator\Document as DocumentValidator; +use Utopia\Database\Validator\Document as DocumentsValidator; use Utopia\Database\Validator\Index as IndexValidator; use Utopia\Database\Validator\Structure; @@ -3561,10 +3561,10 @@ public function find(string $collection, array $queries = [], ?int $timeout = nu $attributes = $collection->getAttribute('attributes', []); $indexes = $collection->getAttribute('indexes', []); -// $validator = new DocumentValidator($attributes, $indexes); -// if(!$validator->isValid($queries)){ -// throw new Exception($validator->getDescription()); -// } + $validator = new DocumentsValidator($attributes, $indexes); + if(!$validator->isValid($queries)){ + throw new Exception($validator->getDescription()); + } $relationships = \array_filter( $collection->getAttribute('attributes', []), diff --git a/src/Database/Validator/DocumentValidator.php b/src/Database/Validator/DocumentValidator.php new file mode 100644 index 000000000..762f1046e --- /dev/null +++ b/src/Database/Validator/DocumentValidator.php @@ -0,0 +1,41 @@ + '$id', + 'type' => Database::VAR_STRING, + 'array' => false, + ]); + $attributes[] = new \Utopia\Database\Document([ + 'key' => '$createdAt', + 'type' => Database::VAR_DATETIME, + 'array' => false, + ]); + $attributes[] = new \Utopia\Database\Document([ + 'key' => '$updatedAt', + 'type' => Database::VAR_DATETIME, + 'array' => false, + ]); + + $validators = [ + new Select($attributes), + ]; + + parent::__construct(...$validators); + } +} diff --git a/src/Database/Validator/Document.php b/src/Database/Validator/Documents.php similarity index 97% rename from src/Database/Validator/Document.php rename to src/Database/Validator/Documents.php index 26d9fff8d..e08b686e6 100644 --- a/src/Database/Validator/Document.php +++ b/src/Database/Validator/Documents.php @@ -12,7 +12,7 @@ use Utopia\Database\Database; use Utopia\Database\Document as UtopiaDocument; -class Document extends IndexedQueries +class Documents extends IndexedQueries { /** * Expression constructor diff --git a/tests/Database/Validator/QueriesTest.php b/tests/Database/Validator/QueriesTest.php index 31564c3fb..95cf05988 100644 --- a/tests/Database/Validator/QueriesTest.php +++ b/tests/Database/Validator/QueriesTest.php @@ -8,7 +8,7 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; -use Utopia\Database\Validator\Document as DocumentValidator; +use Utopia\Database\Validator\Documents as DocumentsValidator; class QueriesTest extends TestCase { @@ -103,7 +103,7 @@ public function tearDown(): void */ public function testValidQueries(): void { - $validator = new DocumentValidator($this->collection['attributes'], $this->collection['indexes']); + $validator = new DocumentsValidator($this->collection['attributes'], $this->collection['indexes']); $queries = [ 'notEqual("title", ["Iron Man", "Ant Man"])', @@ -142,7 +142,7 @@ public function testValidQueries(): void */ public function testInvalidQueries(): void { - $validator = new DocumentValidator($this->collection['attributes'], $this->collection['indexes']); + $validator = new DocumentsValidator($this->collection['attributes'], $this->collection['indexes']); $queries = ['search("description", "iron")']; $this->assertFalse($validator->isValid($queries)); diff --git a/tests/Database/Validator/QueryTest.php b/tests/Database/Validator/QueryTest.php index fdbbd6a1e..3903f9936 100644 --- a/tests/Database/Validator/QueryTest.php +++ b/tests/Database/Validator/QueryTest.php @@ -104,7 +104,7 @@ public function tearDown(): void public function testQuery(): void { - $validator = new \Utopia\Database\Validator\Document($this->attributes, []); + $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); $this->assertEquals(true, $validator->isValid([Query::parse('equal("$id", ["Iron Man", "Ant Man"])')])); $this->assertEquals(true, $validator->isValid([Query::parse('equal("$id", "Iron Man")')])); @@ -130,7 +130,7 @@ public function testQuery(): void public function testInvalidMethod(): void { - $validator = new \Utopia\Database\Validator\Document($this->attributes, []); + $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); $this->assertEquals(false, $validator->isValid([Query::parse('eqqual("title", "Iron Man")')])); $this->assertEquals('Query method not valid: eqqual', $validator->getDescription()); @@ -138,7 +138,7 @@ public function testInvalidMethod(): void public function testAttributeNotFound(): void { - $validator = new \Utopia\Database\Validator\Document($this->attributes, []); + $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); $response = $validator->isValid([Query::parse('equal("name", "Iron Man")')]); @@ -153,7 +153,7 @@ public function testAttributeNotFound(): void public function testAttributeWrongType(): void { - $validator = new \Utopia\Database\Validator\Document($this->attributes, []); + $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); $response = $validator->isValid([Query::parse('equal("title", 1776)')]); @@ -163,14 +163,14 @@ public function testAttributeWrongType(): void public function testQueryDate(): void { - $validator = new \Utopia\Database\Validator\Document($this->attributes, []); + $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); $response = $validator->isValid([Query::parse('greaterThan("birthDay", "1960-01-01 10:10:10")')]); $this->assertEquals(true, $response); } public function testQueryLimit(): void { - $validator = new \Utopia\Database\Validator\Document($this->attributes, []); + $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); $response = $validator->isValid([Query::parse('limit(25)')]); $this->assertEquals(true, $response); @@ -187,7 +187,7 @@ public function testQueryLimit(): void public function testQueryOffset(): void { - $validator = new \Utopia\Database\Validator\Document($this->attributes, []); + $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); $response = $validator->isValid([Query::parse('offset(25)')]); $this->assertEquals(true, $response); @@ -204,7 +204,7 @@ public function testQueryOffset(): void public function testQueryOrder(): void { - $validator = new \Utopia\Database\Validator\Document($this->attributes, []); + $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); $response = $validator->isValid([Query::parse('orderAsc("title")')]); $this->assertEquals(true, $response); @@ -221,7 +221,7 @@ public function testQueryOrder(): void public function testQueryCursor(): void { - $validator = new \Utopia\Database\Validator\Document($this->attributes, []); + $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); $response = $validator->isValid([Query::parse('cursorAfter("asdf")')]); $this->assertEquals(true, $response); From e56f580f304c70a55f3d75c6d3ab554c7e687789 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 27 Apr 2023 11:18:20 +0300 Subject: [PATCH 11/75] Queries folder --- src/Database/Validator/Queries.php | 1 - .../Document.php} | 9 +- .../Validator/{ => Queries}/Documents.php | 7 +- .../Validator/DocumentsQueriesTest.php | 163 ++++++++++++++++ tests/Database/Validator/QueriesTest.php | 177 +++++------------- tests/Database/Validator/QueriesTest2.php | 82 -------- tests/Database/Validator/QueryTest.php | 45 ++++- 7 files changed, 255 insertions(+), 229 deletions(-) rename src/Database/Validator/{DocumentValidator.php => Queries/Document.php} (88%) rename src/Database/Validator/{ => Queries}/Documents.php (94%) create mode 100644 tests/Database/Validator/DocumentsQueriesTest.php delete mode 100644 tests/Database/Validator/QueriesTest2.php diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index e0a1493c3..558b21ac6 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -2,7 +2,6 @@ namespace Utopia\Database\Validator; -use Utopia\Database\Document; use Utopia\Database\Validator\Query\Base; use Utopia\Validator; use Utopia\Database\Query; diff --git a/src/Database/Validator/DocumentValidator.php b/src/Database/Validator/Queries/Document.php similarity index 88% rename from src/Database/Validator/DocumentValidator.php rename to src/Database/Validator/Queries/Document.php index 762f1046e..550d7d2ca 100644 --- a/src/Database/Validator/DocumentValidator.php +++ b/src/Database/Validator/Queries/Document.php @@ -1,16 +1,15 @@ + */ + protected array $collection = []; + + /** + * @throws Exception + */ + public function setUp(): void + { + $this->collection = [ + '$id' => Database::METADATA, + '$collection' => Database::METADATA, + 'name' => 'movies', + 'attributes' => [ + new Document([ + '$id' => 'title', + 'key' => 'title', + 'type' => Database::VAR_STRING, + 'size' => 256, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]), + new Document([ + '$id' => 'description', + 'key' => 'description', + 'type' => Database::VAR_STRING, + 'size' => 1000000, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]), + new Document([ + '$id' => 'rating', + 'key' => 'rating', + 'type' => Database::VAR_INTEGER, + 'size' => 5, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]), + new Document([ + '$id' => 'price', + 'key' => 'price', + 'type' => Database::VAR_FLOAT, + 'size' => 5, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]) + ], + 'indexes' => [ + new Document([ + '$id' => ID::custom('testindex2'), + 'type' => 'key', + 'attributes' => [ + 'title', + 'description', + 'price' + ], + 'orders' => [ + 'ASC', + 'DESC' + ], + ]), + new Document([ + '$id' => ID::custom('testindex3'), + 'type' => 'fulltext', + 'attributes' => [ + 'title' + ], + 'orders' => [] + ]), + ], + ]; + } + + public function tearDown(): void + { + } + + /** + * @throws Exception + */ + public function testValidQueries(): void + { + $validator = new DocumentsQueries($this->collection['attributes'], $this->collection['indexes']); + + $queries = [ + 'notEqual("title", ["Iron Man", "Ant Man"])', + 'equal("description", "Best movie ever")', + 'lessThanEqual("price", 6.50)', + 'lessThan("price", 6.50)', + 'greaterThan("rating", 4)', + 'greaterThanEqual("rating", 6)', + 'between("price", 1.50, 6.50)', + 'search("title", "SEO")', + 'startsWith("title", "Good")', + 'endsWith("title", "Night")', + 'isNull("title")', + 'isNotNull("title")', + 'cursorAfter("a")', + 'cursorBefore("b")', // Todo: This should fail? + 'orderAsc("title")', + 'limit(10)', + 'offset(10)', + ]; + + $queries[] = Query::orderDesc(''); + $this->assertEquals(true, $validator->isValid($queries)); + + $queries = ['search("description", "iron")']; + $this->assertFalse($validator->isValid($queries)); + $this->assertEquals('Searching by attribute "description" requires a fulltext index.', $validator->getDescription()); + + $queries = ['equal("not_found", 4)']; + $this->assertEquals(false, $validator->isValid($queries)); + $this->assertEquals('Query not valid: Attribute not found in schema: not_found', $validator->getDescription()); + } + + /** + * @throws Exception + */ + public function testInvalidQueries(): void + { + $validator = new DocumentsQueries($this->collection['attributes'], $this->collection['indexes']); + + $queries = ['search("description", "iron")']; + $this->assertFalse($validator->isValid($queries)); + $this->assertEquals('Searching by attribute "description" requires a fulltext index.', $validator->getDescription()); + + $queries = ['equal("not_found", 4)']; + $this->assertEquals(false, $validator->isValid($queries)); + $this->assertEquals('Query not valid: Attribute not found in schema: not_found', $validator->getDescription()); + + $queries = ['limit(-1)']; + $this->assertEquals(false, $validator->isValid($queries)); + $this->assertEquals('Query not valid: Invalid limit: Value must be a valid range between 1 and 9,223,372,036,854,775,808', $validator->getDescription()); + + $queries = ['equal("title", [])']; // empty array + $this->assertEquals(false, $validator->isValid($queries)); + $this->assertEquals('Query not valid: equal queries require at least one value.', $validator->getDescription()); + } +} diff --git a/tests/Database/Validator/QueriesTest.php b/tests/Database/Validator/QueriesTest.php index 95cf05988..b5e1f47c5 100644 --- a/tests/Database/Validator/QueriesTest.php +++ b/tests/Database/Validator/QueriesTest.php @@ -3,161 +3,80 @@ namespace Utopia\Tests\Validator; use Exception; -use Utopia\Database\Helpers\ID; use PHPUnit\Framework\TestCase; use Utopia\Database\Database; use Utopia\Database\Document; -use Utopia\Database\Query; -use Utopia\Database\Validator\Documents as DocumentsValidator; +use Utopia\Database\Validator\Queries; +use Utopia\Database\Validator\Query\Cursor; +use Utopia\Database\Validator\Query\Filter; +use Utopia\Database\Validator\Query\Limit; +use Utopia\Database\Validator\Query\Offset; +use Utopia\Database\Validator\Query\Order; class QueriesTest extends TestCase { - /** - * @var array - */ - protected array $collection = []; - - /** - * @throws Exception - */ public function setUp(): void { - $this->collection = [ - '$id' => Database::METADATA, - '$collection' => Database::METADATA, - 'name' => 'movies', - 'attributes' => [ - new Document([ - '$id' => 'title', - 'key' => 'title', - 'type' => Database::VAR_STRING, - 'size' => 256, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]), - new Document([ - '$id' => 'description', - 'key' => 'description', - 'type' => Database::VAR_STRING, - 'size' => 1000000, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]), - new Document([ - '$id' => 'rating', - 'key' => 'rating', - 'type' => Database::VAR_INTEGER, - 'size' => 5, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]), - new Document([ - '$id' => 'price', - 'key' => 'price', - 'type' => Database::VAR_FLOAT, - 'size' => 5, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]) - ], - 'indexes' => [ - new Document([ - '$id' => ID::custom('testindex2'), - 'type' => 'key', - 'attributes' => [ - 'title', - 'description', - 'price' - ], - 'orders' => [ - 'ASC', - 'DESC' - ], - ]), - new Document([ - '$id' => ID::custom('testindex3'), - 'type' => 'fulltext', - 'attributes' => [ - 'title' - ], - 'orders' => [] - ]), - ], - ]; } public function tearDown(): void { } - /** - * @throws Exception - */ - public function testValidQueries(): void + public function testEmptyQueries(): void { - $validator = new DocumentsValidator($this->collection['attributes'], $this->collection['indexes']); + $validator = new Queries(); - $queries = [ - 'notEqual("title", ["Iron Man", "Ant Man"])', - 'equal("description", "Best movie ever")', - 'lessThanEqual("price", 6.50)', - 'lessThan("price", 6.50)', - 'greaterThan("rating", 4)', - 'greaterThanEqual("rating", 6)', - 'between("price", 1.50, 6.50)', - 'search("title", "SEO")', - 'startsWith("title", "Good")', - 'endsWith("title", "Night")', - 'isNull("title")', - 'isNotNull("title")', - 'cursorAfter("a")', - 'cursorBefore("b")', // Todo: This should fail? - 'orderAsc("title")', - 'limit(10)', - 'offset(10)', - ]; + $this->assertEquals(true, $validator->isValid([])); + } + + public function testInvalidQuery(): void + { + $validator = new Queries(); - $queries[] = Query::orderDesc(''); - $this->assertEquals(true, $validator->isValid($queries)); + $this->assertEquals(false, $validator->isValid(["this.is.invalid"])); + } - $queries = ['search("description", "iron")']; - $this->assertFalse($validator->isValid($queries)); - $this->assertEquals('Searching by attribute "description" requires a fulltext index.', $validator->getDescription()); + public function testInvalidMethod(): void + { + $validator = new Queries(); + $this->assertEquals(false, $validator->isValid(['equal("attr", "value")'])); - $queries = ['equal("not_found", 4)']; - $this->assertEquals(false, $validator->isValid($queries)); - $this->assertEquals('Query not valid: Attribute not found in schema: not_found', $validator->getDescription()); + $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)'])); } /** * @throws Exception */ - public function testInvalidQueries(): void + public function testValid(): void { - $validator = new DocumentsValidator($this->collection['attributes'], $this->collection['indexes']); - - $queries = ['search("description", "iron")']; - $this->assertFalse($validator->isValid($queries)); - $this->assertEquals('Searching by attribute "description" requires a fulltext index.', $validator->getDescription()); - - $queries = ['equal("not_found", 4)']; - $this->assertEquals(false, $validator->isValid($queries)); - $this->assertEquals('Query not valid: Attribute not found in schema: not_found', $validator->getDescription()); + $attributes = [ + new Document([ + 'key' => 'name', + 'type' => Database::VAR_STRING, + 'array' => false, + ]) + ]; - $queries = ['limit(-1)']; - $this->assertEquals(false, $validator->isValid($queries)); - $this->assertEquals('Query not valid: Invalid limit: Value must be a valid range between 1 and 9,223,372,036,854,775,808', $validator->getDescription()); + $validator = new Queries( + new Cursor(), + new Filter($attributes), + new Limit(), + new Offset(), + new Order($attributes), + ); - $queries = ['equal("title", [])']; // empty array - $this->assertEquals(false, $validator->isValid($queries)); - $this->assertEquals('Query not valid: equal queries require at least one value.', $validator->getDescription()); + $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/Database/Validator/QueriesTest2.php b/tests/Database/Validator/QueriesTest2.php deleted file mode 100644 index 8b1ec337e..000000000 --- a/tests/Database/Validator/QueriesTest2.php +++ /dev/null @@ -1,82 +0,0 @@ -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)'])); - } - - /** - * @throws Exception - */ - 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/Database/Validator/QueryTest.php b/tests/Database/Validator/QueryTest.php index 3903f9936..31d76a1b4 100644 --- a/tests/Database/Validator/QueryTest.php +++ b/tests/Database/Validator/QueryTest.php @@ -102,9 +102,12 @@ public function tearDown(): void { } + /** + * @throws Exception + */ public function testQuery(): void { - $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); + $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); $this->assertEquals(true, $validator->isValid([Query::parse('equal("$id", ["Iron Man", "Ant Man"])')])); $this->assertEquals(true, $validator->isValid([Query::parse('equal("$id", "Iron Man")')])); @@ -128,17 +131,23 @@ public function testQuery(): void $this->assertEquals(true, $validator->isValid([Query::parse('select(["title", "description"])')])); } + /** + * @throws Exception + */ public function testInvalidMethod(): void { - $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); + $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); $this->assertEquals(false, $validator->isValid([Query::parse('eqqual("title", "Iron Man")')])); $this->assertEquals('Query method not valid: eqqual', $validator->getDescription()); } + /** + * @throws Exception + */ public function testAttributeNotFound(): void { - $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); + $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); $response = $validator->isValid([Query::parse('equal("name", "Iron Man")')]); @@ -151,9 +160,12 @@ public function testAttributeNotFound(): void $this->assertEquals('Query not valid: Attribute not found in schema: name', $validator->getDescription()); } + /** + * @throws Exception + */ public function testAttributeWrongType(): void { - $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); + $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); $response = $validator->isValid([Query::parse('equal("title", 1776)')]); @@ -161,16 +173,22 @@ public function testAttributeWrongType(): void $this->assertEquals('Query not valid: Query type does not match expected: string', $validator->getDescription()); } + /** + * @throws Exception + */ public function testQueryDate(): void { - $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); + $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); $response = $validator->isValid([Query::parse('greaterThan("birthDay", "1960-01-01 10:10:10")')]); $this->assertEquals(true, $response); } + /** + * @throws Exception + */ public function testQueryLimit(): void { - $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); + $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); $response = $validator->isValid([Query::parse('limit(25)')]); $this->assertEquals(true, $response); @@ -185,9 +203,12 @@ public function testQueryLimit(): void $this->assertEquals(false, $response); } + /** + * @throws Exception + */ public function testQueryOffset(): void { - $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); + $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); $response = $validator->isValid([Query::parse('offset(25)')]); $this->assertEquals(true, $response); @@ -202,9 +223,12 @@ public function testQueryOffset(): void $this->assertEquals(false, $response); } + /** + * @throws Exception + */ public function testQueryOrder(): void { - $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); + $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); $response = $validator->isValid([Query::parse('orderAsc("title")')]); $this->assertEquals(true, $response); @@ -219,9 +243,12 @@ public function testQueryOrder(): void $this->assertEquals(false, $response); } + /** + * @throws Exception + */ public function testQueryCursor(): void { - $validator = new \Utopia\Database\Validator\Documents($this->attributes, []); + $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); $response = $validator->isValid([Query::parse('cursorAfter("asdf")')]); $this->assertEquals(true, $response); From 9e19f836df7f6407010e69eec36d1935f15679e0 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 27 Apr 2023 18:58:25 +0300 Subject: [PATCH 12/75] DocumentsQueriesValidator name chage --- src/Database/Database.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 419bf3fa8..88f849f57 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -16,7 +16,7 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\Authorization; -use Utopia\Database\Validator\Document as DocumentsValidator; +use Utopia\Database\Validator\Queries\Documents as DocumentsQueriesValidator; use Utopia\Database\Validator\Index as IndexValidator; use Utopia\Database\Validator\Permissions; use Utopia\Database\Validator\Structure; @@ -3666,7 +3666,7 @@ public function find(string $collection, array $queries = [], ?int $timeout = nu $attributes = $collection->getAttribute('attributes', []); $indexes = $collection->getAttribute('indexes', []); - $validator = new DocumentsValidator($attributes, $indexes); + $validator = new DocumentsQueriesValidator($attributes, $indexes); if(!$validator->isValid($queries)){ throw new Exception($validator->getDescription()); } From d0a06ec968a6a7fdc65900d940fea63154441c00 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 30 Apr 2023 09:46:30 +0300 Subject: [PATCH 13/75] remove OrderAttributes --- src/Database/Validator/OrderAttributes.php | 192 ------------ src/Database/Validator/QueriesOrg.php | 222 -------------- src/Database/Validator/QueryOrg.php | 290 ------------------ .../Database/Validator/OrderValidatorTest.php | 145 --------- 4 files changed, 849 deletions(-) delete mode 100644 src/Database/Validator/OrderAttributes.php delete mode 100644 src/Database/Validator/QueriesOrg.php delete mode 100644 src/Database/Validator/QueryOrg.php delete mode 100644 tests/Database/Validator/OrderValidatorTest.php diff --git a/src/Database/Validator/OrderAttributes.php b/src/Database/Validator/OrderAttributes.php deleted file mode 100644 index b4bb1f45b..000000000 --- a/src/Database/Validator/OrderAttributes.php +++ /dev/null @@ -1,192 +0,0 @@ -> - */ - protected array $schema = []; - - /** - * @var array> - */ - protected array $indexes = []; - - /** - * @var bool - */ - protected bool $strict; - - /** - * Expression constructor - * - * @param array $attributes - * @param array $indexes - * @param bool $strict - */ - public function __construct(array $attributes, array $indexes, bool $strict = true) - { - $this->schema[] = [ - 'key' => '$id', - 'array' => false, - 'type' => Database::VAR_STRING, - 'size' => 512 - ]; - - $this->schema[] = [ - 'key' => '$createdAt', - 'array' => false, - 'type' => Database::VAR_INTEGER, - 'size' => 0 - ]; - - $this->schema[] = [ - 'key' => '$updatedAt', - 'array' => false, - 'type' => Database::VAR_INTEGER, - 'size' => 0 - ]; - - foreach ($attributes as $attribute) { - $this->schema[] = $attribute->getArrayCopy(); - } - - $this->indexes[] = [ - 'type' => Database::INDEX_UNIQUE, - 'attributes' => ['$id'] - ]; - - $this->indexes[] = [ - 'type' => Database::INDEX_KEY, - 'attributes' => ['$createdAt'] - ]; - - $this->indexes[] = [ - 'type' => Database::INDEX_KEY, - 'attributes' => ['$updatedAt'] - ]; - - foreach ($indexes as $index) { - $this->indexes[] = $index->getArrayCopy(['attributes', 'type']); - } - - $this->strict = $strict; - } - - /** - * Get Description. - * - * Returns validator description - * - * @return string - */ - public function getDescription(): string - { - return $this->message; - } - - /** - * Is valid. - * - * Returns true if query typed according to schema. - * - * @param mixed $value - * - * @return bool - */ - public function isValid($value): bool - { - foreach ($value as $attribute) { - // Search for attribute in schema - $attributeInSchema = \in_array($attribute, \array_column($this->schema, 'key')); - - if ($attributeInSchema === false) { - $this->message = 'Order attribute not found in schema: ' . $attribute; - return false; - } - } - - $found = null; - - // Return false if attributes do not exactly match an index - if ($this->strict) { - // look for strict match among indexes - foreach ($this->indexes as $index) { - if ($this->arrayMatch($index['attributes'], $value)) { - $found = $index; - } - } - - if (!$found) { - $this->message = 'Index not found: ' . implode(",", $value); - return false; - } - - // search method requires fulltext index - if (in_array(Query::TYPE_SEARCH, $value) && $found['type'] !== Database::INDEX_FULLTEXT) { - $this->message = 'Search method requires fulltext index: ' . implode(",", $value); - return false; - } - } - - return true; - } - /** - * 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; - } - - /** - * 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($indexes) !== count($queries)) { - return false; - } - - // 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; - } -} diff --git a/src/Database/Validator/QueriesOrg.php b/src/Database/Validator/QueriesOrg.php deleted file mode 100644 index 65c31ffb0..000000000 --- a/src/Database/Validator/QueriesOrg.php +++ /dev/null @@ -1,222 +0,0 @@ - -// */ -// protected array $attributes = []; -// -// /** -// * @var array -// */ -// protected array $indexes = []; -// protected bool $strict; -// -// /** -// * Queries constructor -// * -// * @param QueryValidator $validator used to validate each query -// * @param array|null $attributes allowed attributes to be queried -// * @param array|null $indexes available for strict query matching -// * @param bool $strict -// * @throws Exception -// */ -// public function __construct(QueryValidator $validator, ?array $attributes, ?array $indexes, bool $strict = true) -// { -// $this->validator = $validator; -// $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; -// } -// -// $this->strict = $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 -// * -// * In addition, if $strict is true, this returns false if: -// * 1. there is no index with an exact match of the filters -// * 2. 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 -// { -// $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; -// } -// -// if (!$this->strict) { -// return true; -// } -// -// $grouped = Query::groupByType($queries); -// /** @var array $filters */ -// $filters = $grouped['filters']; -// /** @var array $orderAttributes */ -// $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; -// } -// -// /** -// * 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; -// } -// -// /** -// * Is Strict -// * -// * Returns true if strict validation is set -// * -// * @return bool -// */ -// public function isStrict(): bool -// { -// return $this->strict; -// } -// -// /** -// * 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; -// } -//} diff --git a/src/Database/Validator/QueryOrg.php b/src/Database/Validator/QueryOrg.php deleted file mode 100644 index 176c96b14..000000000 --- a/src/Database/Validator/QueryOrg.php +++ /dev/null @@ -1,290 +0,0 @@ -> -// */ -// protected array $schema = []; -// -// protected int $maxLimit; -// protected int $maxOffset; -// protected int $maxValuesCount; -// -// /** -// * Query constructor -// * -// * @param array $attributes -// * @param int $maxLimit -// * @param int $maxOffset -// * @param int $maxValuesCount -// */ -// public function __construct(array $attributes, int $maxLimit = 100, int $maxOffset = 5000, int $maxValuesCount = 100) -// { -// $this->schema['$id'] = [ -// 'key' => '$id', -// 'array' => false, -// 'type' => Database::VAR_STRING, -// 'size' => 512 -// ]; -// -// $this->schema['$createdAt'] = [ -// 'key' => '$createdAt', -// 'array' => false, -// 'type' => Database::VAR_DATETIME, -// 'size' => 0 -// ]; -// -// $this->schema['$updatedAt'] = [ -// 'key' => '$updatedAt', -// 'array' => false, -// 'type' => Database::VAR_DATETIME, -// 'size' => 0 -// ]; -// -// foreach ($attributes as $attribute) { -// $this->schema[(string)$attribute->getAttribute('key')] = $attribute->getArrayCopy(); -// } -// -// $this->maxLimit = $maxLimit; -// $this->maxOffset = $maxOffset; -// $this->maxValuesCount = $maxValuesCount; -// } -// -// /** -// * Get Description. -// * -// * Returns validator description -// * -// * @return string -// */ -// public function getDescription(): string -// { -// return $this->message; -// } -// -// protected function isValidLimit(?int $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(?int $offset): bool -// { -// $validator = new Range(0, $this->maxOffset); -// if ($validator->isValid($offset)) { -// return true; -// } -// -// $this->message = 'Invalid offset: ' . $validator->getDescription(); -// return false; -// } -// -// protected function isValidCursor(?string $cursor): bool -// { -// if ($cursor === null) { -// $this->message = 'Cursor must not be null'; -// return false; -// } -// return true; -// } -// -// protected function isValidAttribute(string $attribute): bool -// { -// // Search for attribute in schema -// if (!isset($this->schema[$attribute])) { -// $this->message = 'Attribute not found in schema: ' . $attribute; -// return false; -// } -// -// return true; -// } -// -// /** -// * @param string $attribute -// * @param array $values -// * @return bool -// */ -// 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) { -// switch ($attributeType) { -// case Database::VAR_DATETIME: -// $condition = gettype($value) === Database::VAR_STRING; -// break; -// case Database::VAR_FLOAT: -// $condition = (gettype($value) === Database::VAR_FLOAT || gettype($value) === Database::VAR_INTEGER); -// break; -// default: -// $condition = gettype($value) === $attributeType; -// break; -// } -// -// if (!$condition) { -// $this->message = 'Query type does not match expected: ' . $attributeType; -// return false; -// } -// } -// -// return true; -// } -// -// /** -// * @param string $attribute -// * @param array $values -// * @return bool -// */ -// 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: ' . DatabaseQuery::TYPE_CONTAINS; -// return false; -// } -// -// return true; -// } -// -// /** -// * @param array $attributes -// * @return bool -// */ -// protected function isValidSelect(array $attributes): bool -// { -// foreach ($attributes as $attribute) { -// if (!$this->isValidAttribute($attribute)) { -// return false; -// } -// } -// -// return true; -// } -// -// /** -// * Is valid. -// * -// * Returns false if: -// * 1. $query has an invalid method -// * 2. limit value is not a number, less than 0, or greater than $maxLimit -// * 3. offset value is not a number, less than 0, or greater than $maxOffset -// * 4. attribute does not exist -// * 5. count of values is greater than $maxValuesCount -// * 6. value type does not match attribute type -// * 6. contains method is used on non-array attribute -// * -// * Otherwise, returns true. -// * -// * @param DatabaseQuery $query -// * -// * @return bool -// */ -// public function isValid($query): bool -// { -// // Validate method -// $method = $query->getMethod(); -// if (!DatabaseQuery::isMethod($method)) { -// $this->message = 'Query method invalid: ' . $method; -// return false; -// } -// -// $attribute = $query->getAttribute(); -// -// switch ($method) { -// case DatabaseQuery::TYPE_LIMIT: -// $limit = $query->getValue(); -// return $this->isValidLimit($limit); -// -// case DatabaseQuery::TYPE_OFFSET: -// $offset = $query->getValue(); -// return $this->isValidOffset($offset); -// -// case DatabaseQuery::TYPE_CURSORAFTER: -// case DatabaseQuery::TYPE_CURSORBEFORE: -// $cursor = $query->getValue(); -// return $this->isValidCursor($cursor); -// -// case DatabaseQuery::TYPE_ORDERASC: -// case DatabaseQuery::TYPE_ORDERDESC: -// // Allow empty string for order attribute so we can order by natural order -// if ($attribute === '') { -// return true; -// } -// return $this->isValidAttribute($attribute); -// -// case DatabaseQuery::TYPE_CONTAINS: -// $values = $query->getValues(); -// return $this->isValidContains($attribute, $values); -// -// case DatabaseQuery::TYPE_SELECT: -// $attributes = $query->getValues(); -// return $this->isValidSelect($attributes); -// -// default: -// // other filter queries -// $values = $query->getValues(); -// return $this->isValidAttributeAndValues($attribute, $values); -// } -// } -// /** -// * 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/tests/Database/Validator/OrderValidatorTest.php b/tests/Database/Validator/OrderValidatorTest.php deleted file mode 100644 index c10258280..000000000 --- a/tests/Database/Validator/OrderValidatorTest.php +++ /dev/null @@ -1,145 +0,0 @@ - - */ - protected array $schema; - - /** - * @var array - */ - protected array $indexesSchema; - - /** - * @var array> - */ - protected array $attributes = [ - [ - '$id' => 'title', - 'key' => 'title', - 'type' => Database::VAR_STRING, - 'size' => 256, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'description', - 'key' => 'description', - 'type' => Database::VAR_STRING, - 'size' => 1000000, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'rating', - 'key' => 'rating', - 'type' => Database::VAR_INTEGER, - 'size' => 5, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'price', - 'key' => 'price', - 'type' => Database::VAR_FLOAT, - 'size' => 5, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'published', - 'key' => 'published', - 'type' => Database::VAR_BOOLEAN, - 'size' => 5, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'tags', - 'key' => 'tags', - 'type' => Database::VAR_STRING, - 'size' => 55, - 'required' => true, - 'signed' => true, - 'array' => true, - 'filters' => [], - ], - ]; - - /** - * @var array> - */ - protected array $indexes = [ - [ - '$id' => 'index1', - 'type' => Database::INDEX_KEY, - 'attributes' => ['title'], - 'lengths' => [256], - 'orders' => ['ASC'], - ], - [ - '$id' => 'index2', - 'type' => Database::INDEX_KEY, - 'attributes' => ['price'], - 'lengths' => [], - 'orders' => ['DESC'], - ], - [ - '$id' => 'index3', - 'type' => Database::INDEX_KEY, - 'attributes' => ['published'], - 'lengths' => [], - 'orders' => ['DESC'], - ], - ]; - - /** - * @throws \Exception - */ - public function setUp(): void - { - // Query validator expects array - foreach ($this->attributes as $attribute) { - $this->schema[] = new Document($attribute); - } - - // Query validator expects array - foreach ($this->indexes as $index) { - $this->indexesSchema[] = new Document($index); - } - } - - public function tearDown(): void - { - } - - public function testQuery(): void - { - $validator = new OrderAttributes($this->schema, $this->indexesSchema); - - $this->assertEquals(true, $validator->isValid(['$id'])); - $this->assertEquals(true, $validator->isValid(['title'])); - $this->assertEquals(true, $validator->isValid(['published'])); - $this->assertEquals(false, $validator->isValid(['_uid'])); - } -} From 0d201d5d32119273f3621111c6f8402eacd04383 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 30 Apr 2023 13:36:19 +0300 Subject: [PATCH 14/75] Test for DocumentQueries --- src/Database/Validator/Query/Select.php | 1 + .../Validator/DocumentQueriesTest.php | 86 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 tests/Database/Validator/DocumentQueriesTest.php diff --git a/src/Database/Validator/Query/Select.php b/src/Database/Validator/Query/Select.php index 44ce03cc3..0cdf52eb6 100644 --- a/src/Database/Validator/Query/Select.php +++ b/src/Database/Validator/Query/Select.php @@ -46,6 +46,7 @@ public function isValid($query): bool // Utopia will validate each nested level during the recursive calls. $attribute = \explode('.', $attribute)[0]; } + if (!isset($this->schema[$attribute]) && $attribute !== '*') { $this->message = 'Attribute not found in schema: ' . $attribute; return false; diff --git a/tests/Database/Validator/DocumentQueriesTest.php b/tests/Database/Validator/DocumentQueriesTest.php new file mode 100644 index 000000000..aa32b9d07 --- /dev/null +++ b/tests/Database/Validator/DocumentQueriesTest.php @@ -0,0 +1,86 @@ + + */ + protected array $collection = []; + + /** + * @throws Exception + */ + public function setUp(): void + { + $this->collection = [ + '$collection' => ID::custom(Database::METADATA), + '$id' => ID::custom('movies'), + 'name' => 'movies', + 'attributes' => [ + new Document([ + '$id' => 'title', + 'key' => 'title', + 'type' => Database::VAR_STRING, + 'size' => 256, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]), + new Document([ + '$id' => 'price', + 'key' => 'price', + 'type' => Database::VAR_FLOAT, + 'size' => 5, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]) + ] + ]; + } + + public function tearDown(): void + { + } + + /** + * @throws Exception + */ + public function testValidQueries(): void + { + $validator = new DocumentQueries($this->collection['attributes']); + + $queries = [ + 'select(["title"])', + ]; + + $this->assertEquals(true, $validator->isValid($queries)); + + $queries[] = Query::select(['price.relation']); + $this->assertEquals(true, $validator->isValid($queries)); + } + + /** + * @throws Exception + */ + public function testInvalidQueries(): void + { + $validator = new DocumentQueries($this->collection['attributes']); + // $queries = [Query::select(['movies.price'])]; + + $queries = [Query::limit(1)]; // We only accept Select queries + $this->assertEquals(false, $validator->isValid($queries)); + } +} From ea66257680c19fcbf02e70a030db5ec7a7527d7a Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 30 Apr 2023 15:03:36 +0300 Subject: [PATCH 15/75] DocumentsQueries Tests --- src/Database/Database.php | 2 +- src/Database/Validator/Queries/Document.php | 2 +- src/Database/Validator/Query/Filter.php | 3 +- tests/Database/Adapter/MariaDBTest.php | 1 + .../Validator/DocumentQueriesTest.php | 2 +- tests/Database/Validator/Query/CursorTest.php | 2 +- tests/Database/Validator/Query/FilterTest.php | 46 +++++++++---------- tests/Database/Validator/Query/OrderTest.php | 2 +- tests/Database/Validator/QueryTest.php | 19 ++++---- 9 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 88f849f57..bcb9893ba 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3667,7 +3667,7 @@ public function find(string $collection, array $queries = [], ?int $timeout = nu $indexes = $collection->getAttribute('indexes', []); $validator = new DocumentsQueriesValidator($attributes, $indexes); - if(!$validator->isValid($queries)){ + if (!$validator->isValid($queries)) { throw new Exception($validator->getDescription()); } diff --git a/src/Database/Validator/Queries/Document.php b/src/Database/Validator/Queries/Document.php index 550d7d2ca..6ff6248b1 100644 --- a/src/Database/Validator/Queries/Document.php +++ b/src/Database/Validator/Queries/Document.php @@ -10,7 +10,7 @@ class Document extends Queries { /** - * @param array $attributes + * @param array $attributes * @throws Exception */ public function __construct(array $attributes) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index d2755a660..ef37c9e01 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -99,7 +99,8 @@ protected function isValidAttributeAndValues(string $attribute, array $values): } } - if($attributeSchema['array'] && !is_array($values)){ + if ($attributeSchema['array'] && !is_array($values)) { + var_dump('Query method only supported on array'); $this->message = 'Query method only supported on array'; return false; } diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php index f14b2882f..2176bc474 100644 --- a/tests/Database/Adapter/MariaDBTest.php +++ b/tests/Database/Adapter/MariaDBTest.php @@ -1,4 +1,5 @@ collection['attributes']); - // $queries = [Query::select(['movies.price'])]; + // $queries = [Query::select(['movies.price'])]; $queries = [Query::limit(1)]; // We only accept Select queries $this->assertEquals(false, $validator->isValid($queries)); diff --git a/tests/Database/Validator/Query/CursorTest.php b/tests/Database/Validator/Query/CursorTest.php index 5f52f516d..a4114351b 100644 --- a/tests/Database/Validator/Query/CursorTest.php +++ b/tests/Database/Validator/Query/CursorTest.php @@ -11,7 +11,7 @@ class CursorTest extends TestCase public function testValue(): void { $validator = new Cursor(); - + // Test for Success $this->assertEquals($validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), true, $validator->getDescription()); $this->assertEquals($validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), true, $validator->getDescription()); diff --git a/tests/Database/Validator/Query/FilterTest.php b/tests/Database/Validator/Query/FilterTest.php index 0c61b8f75..edc3e836b 100644 --- a/tests/Database/Validator/Query/FilterTest.php +++ b/tests/Database/Validator/Query/FilterTest.php @@ -16,7 +16,7 @@ class FilterTest extends TestCase */ public function testValue(): void { - $this->validator = new Filter( + $validator = new Filter( attributes: [ new Document([ 'key' => 'attr', @@ -26,29 +26,29 @@ public function testValue(): void ], ); // Test for Success - $this->assertEquals($this->validator->isValid(Query::between('attr', '1975-12-06', '2050-12-06')), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::isNotNull('attr')), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::isNull('attr')), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::startsWith('attr', 'super')), true, $this->validator->getDescription()); - $this->assertEquals($this->validator->isValid(Query::endsWith('attr', 'man')), true, $this->validator->getDescription()); + $this->assertEquals($validator->isValid(Query::between('attr', '1975-12-06', '2050-12-06')), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::isNotNull('attr')), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::isNull('attr')), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::startsWith('attr', 'super')), true, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::endsWith('attr', 'man')), true, $validator->getDescription()); // Test for Failure - $this->assertEquals($this->validator->isValid(Query::select(['attr'])), false, $this->validator->getDescription()); - $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()); + $this->assertEquals($validator->isValid(Query::select(['attr'])), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::limit(1)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::limit(0)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::limit(100)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::limit(-1)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::limit(101)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::offset(1)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::offset(0)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::offset(5000)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::offset(-1)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::offset(5001)), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::equal('dne', ['v'])), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::equal('', ['v'])), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::orderAsc('attr')), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(Query::orderDesc('attr')), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), false, $validator->getDescription()); + $this->assertEquals($validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), false, $validator->getDescription()); } } diff --git a/tests/Database/Validator/Query/OrderTest.php b/tests/Database/Validator/Query/OrderTest.php index 1224fa5d0..05343426e 100644 --- a/tests/Database/Validator/Query/OrderTest.php +++ b/tests/Database/Validator/Query/OrderTest.php @@ -21,7 +21,7 @@ public function testValue(): void ]), ], ); - + // Test for Success $this->assertEquals($validator->isValid(Query::orderAsc('attr')), true, $validator->getDescription()); $this->assertEquals($validator->isValid(Query::orderAsc('')), true, $validator->getDescription()); diff --git a/tests/Database/Validator/QueryTest.php b/tests/Database/Validator/QueryTest.php index 31d76a1b4..274b0b3e8 100644 --- a/tests/Database/Validator/QueryTest.php +++ b/tests/Database/Validator/QueryTest.php @@ -7,6 +7,7 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; +use Utopia\Database\Validator\Queries\Documents as DocumentsQueries; class QueryTest extends TestCase { @@ -107,7 +108,7 @@ public function tearDown(): void */ public function testQuery(): void { - $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); + $validator = new DocumentsQueries($this->attributes, []); $this->assertEquals(true, $validator->isValid([Query::parse('equal("$id", ["Iron Man", "Ant Man"])')])); $this->assertEquals(true, $validator->isValid([Query::parse('equal("$id", "Iron Man")')])); @@ -136,7 +137,7 @@ public function testQuery(): void */ public function testInvalidMethod(): void { - $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); + $validator = new DocumentsQueries($this->attributes, []); $this->assertEquals(false, $validator->isValid([Query::parse('eqqual("title", "Iron Man")')])); $this->assertEquals('Query method not valid: eqqual', $validator->getDescription()); @@ -147,7 +148,7 @@ public function testInvalidMethod(): void */ public function testAttributeNotFound(): void { - $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); + $validator = new DocumentsQueries($this->attributes, []); $response = $validator->isValid([Query::parse('equal("name", "Iron Man")')]); @@ -165,7 +166,7 @@ public function testAttributeNotFound(): void */ public function testAttributeWrongType(): void { - $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); + $validator = new DocumentsQueries($this->attributes, []); $response = $validator->isValid([Query::parse('equal("title", 1776)')]); @@ -178,7 +179,7 @@ public function testAttributeWrongType(): void */ public function testQueryDate(): void { - $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); + $validator = new DocumentsQueries($this->attributes, []); $response = $validator->isValid([Query::parse('greaterThan("birthDay", "1960-01-01 10:10:10")')]); $this->assertEquals(true, $response); } @@ -188,7 +189,7 @@ public function testQueryDate(): void */ public function testQueryLimit(): void { - $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); + $validator = new DocumentsQueries($this->attributes, []); $response = $validator->isValid([Query::parse('limit(25)')]); $this->assertEquals(true, $response); @@ -208,7 +209,7 @@ public function testQueryLimit(): void */ public function testQueryOffset(): void { - $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); + $validator = new DocumentsQueries($this->attributes, []); $response = $validator->isValid([Query::parse('offset(25)')]); $this->assertEquals(true, $response); @@ -228,7 +229,7 @@ public function testQueryOffset(): void */ public function testQueryOrder(): void { - $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); + $validator = new DocumentsQueries($this->attributes, []); $response = $validator->isValid([Query::parse('orderAsc("title")')]); $this->assertEquals(true, $response); @@ -248,7 +249,7 @@ public function testQueryOrder(): void */ public function testQueryCursor(): void { - $validator = new \Utopia\Database\Validator\Queries\Documents($this->attributes, []); + $validator = new DocumentsQueries($this->attributes, []); $response = $validator->isValid([Query::parse('cursorAfter("asdf")')]); $this->assertEquals(true, $response); From 94714c3a045b2b29e8d4c054956cdce47990632e Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 30 Apr 2023 15:05:03 +0300 Subject: [PATCH 16/75] remove isArray check since is always true --- src/Database/Validator/Query/Filter.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index ef37c9e01..cf2802001 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -99,12 +99,6 @@ protected function isValidAttributeAndValues(string $attribute, array $values): } } - if ($attributeSchema['array'] && !is_array($values)) { - var_dump('Query method only supported on array'); - $this->message = 'Query method only supported on array'; - return false; - } - return true; } From f9e767c0b5d65e25e737cf2d69865910439398ad Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 30 Apr 2023 17:27:06 +0300 Subject: [PATCH 17/75] MariaDBTest.php tests --- tests/Database/Adapter/MariaDBTest.php | 130 ++++++++++++------------- tests/Database/Base.php | 1 + 2 files changed, 66 insertions(+), 65 deletions(-) diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php index 2176bc474..0ceaca1c8 100644 --- a/tests/Database/Adapter/MariaDBTest.php +++ b/tests/Database/Adapter/MariaDBTest.php @@ -1,67 +1,67 @@ connect('redis', 6379); -// $redis->flushAll(); -// $cache = new Cache(new RedisAdapter($redis)); -// -// $database = new Database(new MariaDB($pdo), $cache); -// $database->setDefaultDatabase('utopiaTests'); -// $database->setNamespace('myapp_'.uniqid()); -// -// if ($database->exists('utopiaTests')) { -// $database->delete('utopiaTests'); -// } -// -// $database->create(); -// -// return self::$database = $database; -// } -// -// public static function killDatabase(): void -// { -// self::$database = null; -// } -//} + +namespace Utopia\Tests\Adapter; + +use PDO; +use Redis; +use Utopia\Database\Database; +use Utopia\Database\Adapter\MariaDB; +use Utopia\Cache\Cache; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Tests\Base; + +class MariaDBTest extends Base +{ + public static ?Database $database = null; + + // TODO@kodumbeats hacky way to identify adapters for tests + // Remove once all methods are implemented + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "mariadb"; + } + + /** + * @return Database + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $dbHost = 'mariadb'; + $dbPort = '3306'; + $dbUser = 'root'; + $dbPass = 'password'; + + $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MariaDB::getPDOAttributes()); + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + $cache = new Cache(new RedisAdapter($redis)); + + $database = new Database(new MariaDB($pdo), $cache); + $database->setDefaultDatabase('utopiaTests'); + $database->setNamespace('myapp_'.uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + + $database->create(); + + return self::$database = $database; + } + + public static function killDatabase(): void + { + self::$database = null; + } +} diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 347d0be36..3b6b0511f 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -10286,6 +10286,7 @@ public function testManyToOneRelationshipKeyWithSymbols(): void $this->assertEquals($doc2->getId(), $doc1->getAttribute('$symbols_coll.ection5')[0]->getId()); $this->assertEquals($doc1->getId(), $doc2->getAttribute('$symbols_coll.ection6')->getId()); + } public function testManyToManyRelationshipKeyWithSymbols(): void From 0b7a496dbaec817909a41380728d43c9dc3ecfd4 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 1 May 2023 11:52:04 +0300 Subject: [PATCH 18/75] Get query by type --- src/Database/Query.php | 5 +- tests/Database/Adapter/MariaDBTest.php | 132 ++++++++++++------------- tests/Database/Validator/QueryTest.php | 20 ++++ 3 files changed, 88 insertions(+), 69 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index d92c6adec..0cfea1309 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -698,11 +698,10 @@ public static function endsWith(string $attribute, string $value): self * Filters $queries for $types * * @param array $queries - * @param string ...$types - * + * @param array $types * @return array */ - public static function getByType(array $queries, string ...$types): array + public static function getByType(array $queries, array $types): array { $filtered = []; foreach ($queries as $query) { diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php index 0ceaca1c8..b60c0df31 100644 --- a/tests/Database/Adapter/MariaDBTest.php +++ b/tests/Database/Adapter/MariaDBTest.php @@ -1,67 +1,67 @@ connect('redis', 6379); - $redis->flushAll(); - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new MariaDB($pdo), $cache); - $database->setDefaultDatabase('utopiaTests'); - $database->setNamespace('myapp_'.uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } - - public static function killDatabase(): void - { - self::$database = null; - } -} +// +// +//namespace Utopia\Tests\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Database\Database; +//use Utopia\Database\Adapter\MariaDB; +//use Utopia\Cache\Cache; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Tests\Base; +// +//class MariaDBTest extends Base +//{ +// public static ?Database $database = null; +// +// // TODO@kodumbeats hacky way to identify adapters for tests +// // Remove once all methods are implemented +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "mariadb"; +// } +// +// /** +// * @return Database +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $dbHost = 'mariadb'; +// $dbPort = '3306'; +// $dbUser = 'root'; +// $dbPass = 'password'; +// +// $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MariaDB::getPDOAttributes()); +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new MariaDB($pdo), $cache); +// $database->setDefaultDatabase('utopiaTests'); +// $database->setNamespace('myapp_'.uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +// +// public static function killDatabase(): void +// { +// self::$database = null; +// } +//} diff --git a/tests/Database/Validator/QueryTest.php b/tests/Database/Validator/QueryTest.php index 274b0b3e8..0ca58e350 100644 --- a/tests/Database/Validator/QueryTest.php +++ b/tests/Database/Validator/QueryTest.php @@ -257,4 +257,24 @@ public function testQueryCursor(): void $response = $validator->isValid([Query::parse('cursorAfter()')]); $this->assertEquals(false, $response); } + + /** + * @throws Exception + */ + public function testQueryGetByType(): void + { + $queries = [ + Query::equal('key', ['value']), + Query::select(['attr1', 'attr2']), + Query::cursorBefore(new Document([])), + Query::cursorAfter(new Document([])), + ]; + + $queries = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $this->assertCount(2, $queries); + foreach ($queries as $query){ + $this->assertEquals(true, in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE])); + } + } + } From 3507239b1505c11d2038a3f5788b3b6e7defb030 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 1 May 2023 15:07:44 +0300 Subject: [PATCH 19/75] var_dumps for debug --- src/Database/Database.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index bcb9893ba..c518ad0b6 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3666,6 +3666,11 @@ public function find(string $collection, array $queries = [], ?int $timeout = nu $attributes = $collection->getAttribute('attributes', []); $indexes = $collection->getAttribute('indexes', []); + var_dump('-------------------------------'); + var_dump($queries); + var_dump($attributes); + var_dump('-------------------------------'); + $validator = new DocumentsQueriesValidator($attributes, $indexes); if (!$validator->isValid($queries)) { throw new Exception($validator->getDescription()); From 1f05340a26e273e72a3106b0e50aa1e75daae505 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 1 May 2023 16:17:03 +0300 Subject: [PATCH 20/75] fallback for empty key --- src/Database/Validator/Query/Filter.php | 2 +- src/Database/Validator/Query/Order.php | 2 +- src/Database/Validator/Query/Select.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index cf2802001..af5cd31aa 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -24,7 +24,7 @@ class Filter extends Base public function __construct(array $attributes = [], int $maxValuesCount = 100) { foreach ($attributes as $attribute) { - $this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); + $this->schema[$attribute->getAttribute('key', $attribute->getAttribute('$id'))] = $attribute->getArrayCopy(); } $this->maxValuesCount = $maxValuesCount; diff --git a/src/Database/Validator/Query/Order.php b/src/Database/Validator/Query/Order.php index e22374632..c4c275981 100644 --- a/src/Database/Validator/Query/Order.php +++ b/src/Database/Validator/Query/Order.php @@ -18,7 +18,7 @@ class Order extends Base public function __construct(array $attributes = []) { foreach ($attributes as $attribute) { - $this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); + $this->schema[$attribute->getAttribute('key', $attribute->getAttribute('$id'))] = $attribute->getArrayCopy(); } } diff --git a/src/Database/Validator/Query/Select.php b/src/Database/Validator/Query/Select.php index 0cdf52eb6..29d0896cf 100644 --- a/src/Database/Validator/Query/Select.php +++ b/src/Database/Validator/Query/Select.php @@ -18,7 +18,7 @@ class Select extends Base public function __construct(array $attributes = []) { foreach ($attributes as $attribute) { - $this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); + $this->schema[$attribute->getAttribute('key', $attribute->getAttribute('$id'))] = $attribute->getArrayCopy(); } } From fa28aac8e11bf1fa95aaaf5bb787fa879c01f7bd Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 1 May 2023 17:05:29 +0300 Subject: [PATCH 21/75] remove fallback --- src/Database/Validator/Query/Select.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Validator/Query/Select.php b/src/Database/Validator/Query/Select.php index 29d0896cf..0cdf52eb6 100644 --- a/src/Database/Validator/Query/Select.php +++ b/src/Database/Validator/Query/Select.php @@ -18,7 +18,7 @@ class Select extends Base public function __construct(array $attributes = []) { foreach ($attributes as $attribute) { - $this->schema[$attribute->getAttribute('key', $attribute->getAttribute('$id'))] = $attribute->getArrayCopy(); + $this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); } } From 9b693eb2b28ec789cce815994b133ca42b21cd0b Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 1 May 2023 17:10:12 +0300 Subject: [PATCH 22/75] remove fallback --- src/Database/Validator/Query/Filter.php | 2 +- src/Database/Validator/Query/Order.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index af5cd31aa..cf2802001 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -24,7 +24,7 @@ class Filter extends Base public function __construct(array $attributes = [], int $maxValuesCount = 100) { foreach ($attributes as $attribute) { - $this->schema[$attribute->getAttribute('key', $attribute->getAttribute('$id'))] = $attribute->getArrayCopy(); + $this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); } $this->maxValuesCount = $maxValuesCount; diff --git a/src/Database/Validator/Query/Order.php b/src/Database/Validator/Query/Order.php index c4c275981..e22374632 100644 --- a/src/Database/Validator/Query/Order.php +++ b/src/Database/Validator/Query/Order.php @@ -18,7 +18,7 @@ class Order extends Base public function __construct(array $attributes = []) { foreach ($attributes as $attribute) { - $this->schema[$attribute->getAttribute('key', $attribute->getAttribute('$id'))] = $attribute->getArrayCopy(); + $this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); } } From 67cc4193aa04839b858b4df925564ad85e8a837d Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 1 May 2023 20:24:52 +0300 Subject: [PATCH 23/75] dbg --- src/Database/Database.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index c518ad0b6..41b1c6e8a 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3661,12 +3661,13 @@ public function find(string $collection, array $queries = [], ?int $timeout = nu if (!\is_null($timeout) && $timeout <= 0) { throw new Exception('Timeout must be greater than 0'); } - + var_dump('-------------------------------'); + var_dump($collection); $collection = $this->silent(fn () => $this->getCollection($collection)); $attributes = $collection->getAttribute('attributes', []); $indexes = $collection->getAttribute('indexes', []); - var_dump('-------------------------------'); + var_dump($queries); var_dump($attributes); var_dump('-------------------------------'); From cd24a59ed57991145c47222d5b4b7927a04c45a7 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 2 May 2023 09:35:01 +0300 Subject: [PATCH 24/75] use $id instead of key --- src/Database/Validator/Queries/Document.php | 7 ++++++- src/Database/Validator/Queries/Documents.php | 19 ++++++++++++------- src/Database/Validator/Query/Filter.php | 3 ++- src/Database/Validator/Query/Order.php | 3 ++- src/Database/Validator/Query/Select.php | 3 ++- .../Database/Validator/IndexedQueriesTest.php | 1 + tests/Database/Validator/QueriesTest.php | 1 + tests/Database/Validator/Query/FilterTest.php | 1 + tests/Database/Validator/Query/OrderTest.php | 1 + tests/Database/Validator/Query/SelectTest.php | 1 + 10 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/Database/Validator/Queries/Document.php b/src/Database/Validator/Queries/Document.php index 6ff6248b1..673411f50 100644 --- a/src/Database/Validator/Queries/Document.php +++ b/src/Database/Validator/Queries/Document.php @@ -16,17 +16,22 @@ class Document extends Queries public function __construct(array $attributes) { $attributes[] = new \Utopia\Database\Document([ + '$id' => '$id', 'key' => '$id', 'type' => Database::VAR_STRING, 'array' => false, ]); + $attributes[] = new \Utopia\Database\Document([ + '$id' => '$createdAt', 'key' => '$createdAt', 'type' => Database::VAR_DATETIME, 'array' => false, ]); + $attributes[] = new \Utopia\Database\Document([ - 'key' => '$updatedAt', + '$id' => '$createdAt', + 'key' => '$createdAt', 'type' => Database::VAR_DATETIME, 'array' => false, ]); diff --git a/src/Database/Validator/Queries/Documents.php b/src/Database/Validator/Queries/Documents.php index 886b39b5f..af6ef06a1 100644 --- a/src/Database/Validator/Queries/Documents.php +++ b/src/Database/Validator/Queries/Documents.php @@ -4,7 +4,7 @@ use Exception; use Utopia\Database\Database; -use Utopia\Database\Document as UtopiaDocument; +use Utopia\Database\Document; use Utopia\Database\Validator\IndexedQueries; use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\Query\Filter; @@ -18,24 +18,29 @@ class Documents extends IndexedQueries /** * Expression constructor * - * @param UtopiaDocument[] $attributes - * @param UtopiaDocument[] $indexes + * @param array $attributes + * @param array $indexes * @throws Exception */ public function __construct(array $attributes, array $indexes) { - $attributes[] = new UtopiaDocument([ + $attributes[] = new Document([ + '$id' => '$id', 'key' => '$id', 'type' => Database::VAR_STRING, 'array' => false, ]); - $attributes[] = new UtopiaDocument([ + + $attributes[] = new Document([ + '$id' => '$createdAt', 'key' => '$createdAt', 'type' => Database::VAR_DATETIME, 'array' => false, ]); - $attributes[] = new UtopiaDocument([ - 'key' => '$updatedAt', + + $attributes[] = new Document([ + '$id' => '$createdAt', + 'key' => '$createdAt', 'type' => Database::VAR_DATETIME, 'array' => false, ]); diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index cf2802001..5b9966044 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -24,7 +24,8 @@ class Filter extends Base public function __construct(array $attributes = [], int $maxValuesCount = 100) { foreach ($attributes as $attribute) { - $this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); + $this->schema[$attribute->getAttribute('$id')] = $attribute->getArrayCopy(); + //$this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); } $this->maxValuesCount = $maxValuesCount; diff --git a/src/Database/Validator/Query/Order.php b/src/Database/Validator/Query/Order.php index e22374632..006fb0620 100644 --- a/src/Database/Validator/Query/Order.php +++ b/src/Database/Validator/Query/Order.php @@ -18,7 +18,8 @@ class Order extends Base public function __construct(array $attributes = []) { foreach ($attributes as $attribute) { - $this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); + $this->schema[$attribute->getAttribute('$id')] = $attribute->getArrayCopy(); + //$this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); } } diff --git a/src/Database/Validator/Query/Select.php b/src/Database/Validator/Query/Select.php index 0cdf52eb6..d19945753 100644 --- a/src/Database/Validator/Query/Select.php +++ b/src/Database/Validator/Query/Select.php @@ -18,7 +18,8 @@ class Select extends Base public function __construct(array $attributes = []) { foreach ($attributes as $attribute) { - $this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); + $this->schema[$attribute->getAttribute('$id')] = $attribute->getArrayCopy(); + //$this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); } } diff --git a/tests/Database/Validator/IndexedQueriesTest.php b/tests/Database/Validator/IndexedQueriesTest.php index f6c59131b..18bc115d5 100644 --- a/tests/Database/Validator/IndexedQueriesTest.php +++ b/tests/Database/Validator/IndexedQueriesTest.php @@ -55,6 +55,7 @@ public function testValid(): void { $attributes = [ new Document([ + '$id' => 'name', 'key' => 'name', 'type' => Database::VAR_STRING, 'array' => false, diff --git a/tests/Database/Validator/QueriesTest.php b/tests/Database/Validator/QueriesTest.php index b5e1f47c5..7ea4b6287 100644 --- a/tests/Database/Validator/QueriesTest.php +++ b/tests/Database/Validator/QueriesTest.php @@ -59,6 +59,7 @@ public function testValid(): void { $attributes = [ new Document([ + '$id' => 'name', 'key' => 'name', 'type' => Database::VAR_STRING, 'array' => false, diff --git a/tests/Database/Validator/Query/FilterTest.php b/tests/Database/Validator/Query/FilterTest.php index edc3e836b..714b24ff1 100644 --- a/tests/Database/Validator/Query/FilterTest.php +++ b/tests/Database/Validator/Query/FilterTest.php @@ -19,6 +19,7 @@ public function testValue(): void $validator = new Filter( attributes: [ new Document([ + '$id' => 'attr', 'key' => 'attr', 'type' => Database::VAR_STRING, 'array' => false, diff --git a/tests/Database/Validator/Query/OrderTest.php b/tests/Database/Validator/Query/OrderTest.php index 05343426e..c0b914b12 100644 --- a/tests/Database/Validator/Query/OrderTest.php +++ b/tests/Database/Validator/Query/OrderTest.php @@ -15,6 +15,7 @@ public function testValue(): void $validator = new Order( attributes: [ new Document([ + '$id' => 'attr', 'key' => 'attr', 'type' => Database::VAR_STRING, 'array' => false, diff --git a/tests/Database/Validator/Query/SelectTest.php b/tests/Database/Validator/Query/SelectTest.php index 58520a357..341755712 100644 --- a/tests/Database/Validator/Query/SelectTest.php +++ b/tests/Database/Validator/Query/SelectTest.php @@ -15,6 +15,7 @@ public function testValue(): void $validator = new Select( attributes: [ new Document([ + '$id' => 'attr', 'key' => 'attr', 'type' => Database::VAR_STRING, 'array' => false, From cc0069b159210b5d6aebf360b795e51347364269 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 2 May 2023 12:04:37 +0300 Subject: [PATCH 25/75] Fallback for key --- src/Database/Validator/Query/Filter.php | 3 +-- src/Database/Validator/Query/Order.php | 3 +-- src/Database/Validator/Query/Select.php | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index 5b9966044..af5cd31aa 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -24,8 +24,7 @@ class Filter extends Base public function __construct(array $attributes = [], int $maxValuesCount = 100) { foreach ($attributes as $attribute) { - $this->schema[$attribute->getAttribute('$id')] = $attribute->getArrayCopy(); - //$this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); + $this->schema[$attribute->getAttribute('key', $attribute->getAttribute('$id'))] = $attribute->getArrayCopy(); } $this->maxValuesCount = $maxValuesCount; diff --git a/src/Database/Validator/Query/Order.php b/src/Database/Validator/Query/Order.php index 006fb0620..c4c275981 100644 --- a/src/Database/Validator/Query/Order.php +++ b/src/Database/Validator/Query/Order.php @@ -18,8 +18,7 @@ class Order extends Base public function __construct(array $attributes = []) { foreach ($attributes as $attribute) { - $this->schema[$attribute->getAttribute('$id')] = $attribute->getArrayCopy(); - //$this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); + $this->schema[$attribute->getAttribute('key', $attribute->getAttribute('$id'))] = $attribute->getArrayCopy(); } } diff --git a/src/Database/Validator/Query/Select.php b/src/Database/Validator/Query/Select.php index d19945753..29d0896cf 100644 --- a/src/Database/Validator/Query/Select.php +++ b/src/Database/Validator/Query/Select.php @@ -18,8 +18,7 @@ class Select extends Base public function __construct(array $attributes = []) { foreach ($attributes as $attribute) { - $this->schema[$attribute->getAttribute('$id')] = $attribute->getArrayCopy(); - //$this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy(); + $this->schema[$attribute->getAttribute('key', $attribute->getAttribute('$id'))] = $attribute->getArrayCopy(); } } From 025a8fd3a87ac79ce54b1991bd8ab9ec921ccf96 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 2 May 2023 15:54:44 +0300 Subject: [PATCH 26/75] little green --- src/Database/Database.php | 8 +- src/Database/Validator/Queries/Document.php | 4 +- src/Database/Validator/Queries/Documents.php | 4 +- src/Database/Validator/Query/Filter.php | 22 +++- tests/Database/Adapter/MariaDBTest.php | 132 +++++++++---------- tests/Database/Base.php | 2 + 6 files changed, 89 insertions(+), 83 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 41b1c6e8a..bcb9893ba 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3661,17 +3661,11 @@ public function find(string $collection, array $queries = [], ?int $timeout = nu if (!\is_null($timeout) && $timeout <= 0) { throw new Exception('Timeout must be greater than 0'); } - var_dump('-------------------------------'); - var_dump($collection); + $collection = $this->silent(fn () => $this->getCollection($collection)); $attributes = $collection->getAttribute('attributes', []); $indexes = $collection->getAttribute('indexes', []); - - var_dump($queries); - var_dump($attributes); - var_dump('-------------------------------'); - $validator = new DocumentsQueriesValidator($attributes, $indexes); if (!$validator->isValid($queries)) { throw new Exception($validator->getDescription()); diff --git a/src/Database/Validator/Queries/Document.php b/src/Database/Validator/Queries/Document.php index 673411f50..d6f8792c9 100644 --- a/src/Database/Validator/Queries/Document.php +++ b/src/Database/Validator/Queries/Document.php @@ -30,8 +30,8 @@ public function __construct(array $attributes) ]); $attributes[] = new \Utopia\Database\Document([ - '$id' => '$createdAt', - 'key' => '$createdAt', + '$id' => '$updatedAt', + 'key' => '$updatedAt', 'type' => Database::VAR_DATETIME, 'array' => false, ]); diff --git a/src/Database/Validator/Queries/Documents.php b/src/Database/Validator/Queries/Documents.php index af6ef06a1..78223e323 100644 --- a/src/Database/Validator/Queries/Documents.php +++ b/src/Database/Validator/Queries/Documents.php @@ -39,8 +39,8 @@ public function __construct(array $attributes, array $indexes) ]); $attributes[] = new Document([ - '$id' => '$createdAt', - 'key' => '$createdAt', + '$id' => '$updatedAt', + 'key' => '$updatedAt', 'type' => Database::VAR_DATETIME, 'array' => false, ]); diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index af5cd31aa..9a95d4d63 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -17,6 +17,8 @@ class Filter extends Base private int $maxValuesCount; + public static bool $NESTING_QUERIES = false; + /** * @param array $attributes * @param int $maxValuesCount @@ -37,16 +39,20 @@ public function __construct(array $attributes = [], int $maxValuesCount = 100) protected function isValidAttribute(string $attribute): bool { if (\str_contains($attribute, '.')) { + + // Check for special symbol `.` + if (isset($this->schema[$attribute])) { + return true; + } + // For relationships, just validate the top level. // Utopia will validate each nested level during the recursive calls. $attribute = \explode('.', $attribute)[0]; - // TODO: This must be taken care of in appwrite now... - -// if (isset($this->schema[$attribute])) { -// $this->message = 'Cannot query nested attribute on: ' . $attribute; -// return false; -// } + if (!Filter::$NESTING_QUERIES && isset($this->schema[$attribute])) { + $this->message = 'Cannot query nested attribute on: ' . $attribute; + return false; + } } // Search for attribute in schema @@ -70,6 +76,10 @@ protected function isValidAttributeAndValues(string $attribute, array $values): } if (\str_contains($attribute, '.')) { + // Check for special symbol `.` + if (isset($this->schema[$attribute])) { + return true; + } // For relationships, just validate the top level. // Utopia will validate each nested level during the recursive calls. $attribute = \explode('.', $attribute)[0]; diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php index b60c0df31..0ceaca1c8 100644 --- a/tests/Database/Adapter/MariaDBTest.php +++ b/tests/Database/Adapter/MariaDBTest.php @@ -1,67 +1,67 @@ connect('redis', 6379); -// $redis->flushAll(); -// $cache = new Cache(new RedisAdapter($redis)); -// -// $database = new Database(new MariaDB($pdo), $cache); -// $database->setDefaultDatabase('utopiaTests'); -// $database->setNamespace('myapp_'.uniqid()); -// -// if ($database->exists('utopiaTests')) { -// $database->delete('utopiaTests'); -// } -// -// $database->create(); -// -// return self::$database = $database; -// } -// -// public static function killDatabase(): void -// { -// self::$database = null; -// } -//} + + +namespace Utopia\Tests\Adapter; + +use PDO; +use Redis; +use Utopia\Database\Database; +use Utopia\Database\Adapter\MariaDB; +use Utopia\Cache\Cache; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Tests\Base; + +class MariaDBTest extends Base +{ + public static ?Database $database = null; + + // TODO@kodumbeats hacky way to identify adapters for tests + // Remove once all methods are implemented + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "mariadb"; + } + + /** + * @return Database + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $dbHost = 'mariadb'; + $dbPort = '3306'; + $dbUser = 'root'; + $dbPass = 'password'; + + $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MariaDB::getPDOAttributes()); + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + $cache = new Cache(new RedisAdapter($redis)); + + $database = new Database(new MariaDB($pdo), $cache); + $database->setDefaultDatabase('utopiaTests'); + $database->setNamespace('myapp_'.uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + + $database->create(); + + return self::$database = $database; + } + + public static function killDatabase(): void + { + self::$database = null; + } +} diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 3b6b0511f..5044aa8be 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -21,6 +21,7 @@ use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\Database\Validator\Query\Filter; use Utopia\Database\Validator\Structure; use Utopia\Validator\Range; use Utopia\Database\Exception\Structure as StructureException; @@ -42,6 +43,7 @@ abstract protected static function getAdapterName(): string; public function setUp(): void { Authorization::setRole('any'); + Filter::$NESTING_QUERIES = true; } public function tearDown(): void From 501dbc060b7a109a9aab32b603389cc0b94100b4 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 2 May 2023 18:43:10 +0300 Subject: [PATCH 27/75] Select dots failing --- src/Database/Validator/Query/Select.php | 6 + tests/Database/Base.php | 185 +++++++++++++----------- 2 files changed, 108 insertions(+), 83 deletions(-) diff --git a/src/Database/Validator/Query/Select.php b/src/Database/Validator/Query/Select.php index 29d0896cf..ec55962bb 100644 --- a/src/Database/Validator/Query/Select.php +++ b/src/Database/Validator/Query/Select.php @@ -42,6 +42,12 @@ public function isValid($query): bool foreach ($query->getValues() as $attribute) { if (\str_contains($attribute, '.')) { + + //special symbols with `dots` + if(isset($this->schema[$attribute])){ + continue; + } + // For relationships, just validate the top level. // Utopia will validate each nested level during the recursive calls. $attribute = \explode('.', $attribute)[0]; diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 5044aa8be..3b62b7019 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -10250,6 +10250,25 @@ public function testOneToManyRelationshipKeyWithSymbols(): void $this->assertEquals($doc1->getId(), $doc2->getAttribute('$symbols_coll.ection4')[0]->getId()); } + public function testSymbolDots(): void + { + static::getDatabase()->createCollection('dots'); + + $this->assertTrue(static::getDatabase()->createAttribute( + collection: 'dots', + id: 'dots.name', + type: Database::VAR_STRING, + size: 255, + required: false + )); + + // todo: why is this failing + static::getDatabase()->find('dots', [ + Query::select(['dots.name']), + ]); + + } + public function testManyToOneRelationshipKeyWithSymbols(): void { if (!static::getDatabase()->getAdapter()->getSupportForRelationships()) { @@ -11113,89 +11132,89 @@ public function testEvents(): void $database->delete('hellodb'); }); } -// -// public function testEmptyOperatorValues(): void -// { -// try { -// static::getDatabase()->findOne('documents', [ -// Query::equal('string', []), -// ]); -// $this->fail('Failed to throw exception'); -// } catch (Exception $e) { -// $this->assertInstanceOf(Exception::class, $e); -// $this->assertEquals('Query not valid: equal queries require at least one value.', $e->getMessage()); -// } -// -// try { -// static::getDatabase()->findOne('documents', [ -// Query::search('string', null), -// ]); -// $this->fail('Failed to throw exception'); -// } catch (Exception $e) { -// $this->assertInstanceOf(Exception::class, $e); -// $this->assertEquals('Query not valid: Query type does not match expected: string', $e->getMessage()); -// } -// -// try { -// static::getDatabase()->findOne('documents', [ -// Query::notEqual('string', []), -// ]); -// $this->fail('Failed to throw exception'); -// } catch (Exception $e) { -// $this->assertInstanceOf(Exception::class, $e); -// $this->assertEquals('Query not valid: notEqual queries require at least one value.', $e->getMessage()); -// } -// -// try { -// static::getDatabase()->findOne('documents', [ -// Query::lessThan('string', []), -// ]); -// $this->fail('Failed to throw exception'); -// } catch (Exception $e) { -// $this->assertInstanceOf(Exception::class, $e); -// $this->assertEquals('Query not valid: lessThan queries require at least one value.', $e->getMessage()); -// } -// -// try { -// static::getDatabase()->findOne('documents', [ -// Query::lessThanEqual('string', []), -// ]); -// $this->fail('Failed to throw exception'); -// } catch (Exception $e) { -// $this->assertInstanceOf(Exception::class, $e); -// $this->assertEquals('Query not valid: lessThanEqual queries require at least one value.', $e->getMessage()); -// } -// -// try { -// static::getDatabase()->findOne('documents', [ -// Query::greaterThan('string', []), -// ]); -// $this->fail('Failed to throw exception'); -// } catch (Exception $e) { -// $this->assertInstanceOf(Exception::class, $e); -// $this->assertEquals('Query not valid: greaterThan queries require at least one value.', $e->getMessage()); -// } -// -// try { -// static::getDatabase()->findOne('documents', [ -// Query::greaterThanEqual('string', []), -// ]); -// $this->fail('Failed to throw exception'); -// } catch (Exception $e) { -// $this->assertInstanceOf(Exception::class, $e); -// $this->assertEquals('Query not valid: greaterThanEqual queries require at least one value.', $e->getMessage()); -// } -// -// try { -// static::getDatabase()->findOne('documents', [ -// Query::contains('string', []), -// ]); -// $this->fail('Failed to throw exception'); -// } catch (Exception $e) { -// $this->assertInstanceOf(Exception::class, $e); -// $this->assertEquals('Query not valid: contains queries require at least one value.', $e->getMessage()); -// } -// } + + public function testEmptyOperatorValues(): void + { + try { + static::getDatabase()->findOne('documents', [ + Query::equal('string', []), + ]); + $this->fail('Failed to throw exception'); + } catch (Exception $e) { + $this->assertInstanceOf(Exception::class, $e); + $this->assertEquals('Query not valid: equal queries require at least one value.', $e->getMessage()); + } + + try { + static::getDatabase()->findOne('documents', [ + Query::search('string', null), + ]); + $this->fail('Failed to throw exception'); + } catch (Exception $e) { + $this->assertInstanceOf(Exception::class, $e); + $this->assertEquals('Query not valid: Query type does not match expected: string', $e->getMessage()); + } + + try { + static::getDatabase()->findOne('documents', [ + Query::notEqual('string', []), + ]); + $this->fail('Failed to throw exception'); + } catch (Exception $e) { + $this->assertInstanceOf(Exception::class, $e); + $this->assertEquals('Query not valid: notEqual queries require at least one value.', $e->getMessage()); + } + + try { + static::getDatabase()->findOne('documents', [ + Query::lessThan('string', []), + ]); + $this->fail('Failed to throw exception'); + } catch (Exception $e) { + $this->assertInstanceOf(Exception::class, $e); + $this->assertEquals('Query not valid: lessThan queries require at least one value.', $e->getMessage()); + } + + try { + static::getDatabase()->findOne('documents', [ + Query::lessThanEqual('string', []), + ]); + $this->fail('Failed to throw exception'); + } catch (Exception $e) { + $this->assertInstanceOf(Exception::class, $e); + $this->assertEquals('Query not valid: lessThanEqual queries require at least one value.', $e->getMessage()); + } + + try { + static::getDatabase()->findOne('documents', [ + Query::greaterThan('string', []), + ]); + $this->fail('Failed to throw exception'); + } catch (Exception $e) { + $this->assertInstanceOf(Exception::class, $e); + $this->assertEquals('Query not valid: greaterThan queries require at least one value.', $e->getMessage()); + } + + try { + static::getDatabase()->findOne('documents', [ + Query::greaterThanEqual('string', []), + ]); + $this->fail('Failed to throw exception'); + } catch (Exception $e) { + $this->assertInstanceOf(Exception::class, $e); + $this->assertEquals('Query not valid: greaterThanEqual queries require at least one value.', $e->getMessage()); + } + + try { + static::getDatabase()->findOne('documents', [ + Query::contains('string', []), + ]); + $this->fail('Failed to throw exception'); + } catch (Exception $e) { + $this->assertInstanceOf(Exception::class, $e); + $this->assertEquals('Query not valid: contains queries require at least one value.', $e->getMessage()); + } + } public function testLast(): void { From d131ad4f68084c72dd1afaf25f584e1d561b3b6d Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 2 May 2023 18:44:22 +0300 Subject: [PATCH 28/75] Select dots failing --- tests/Database/Base.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 3b62b7019..fee5cf05f 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -10263,9 +10263,9 @@ public function testSymbolDots(): void )); // todo: why is this failing - static::getDatabase()->find('dots', [ - Query::select(['dots.name']), - ]); +// static::getDatabase()->find('dots', [ +// Query::select(['dots.name']), +// ]); } From 2b2802d0aca30009127535255957102b6678bd77 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 2 May 2023 19:51:29 +0300 Subject: [PATCH 29/75] allowing false as value parameter --- src/Database/Validator/Query/Filter.php | 3 +- .../Validator/DocumentsQueriesTest.php | 30 ++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index 9a95d4d63..572ebb3f8 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -139,7 +139,8 @@ public function isValid($query): bool case Query::TYPE_BETWEEN: case Query::TYPE_CONTAINS: // todo: What to do about unsupported operators? $values = $query->getValues(); - if (empty($values) || (isset($values[0]) && empty($values[0]))) { + + if (is_null($values) || (is_array($values) && isset($values[0]) && empty($values[0]) && $values[0] !== false)) { $this->message = $method . ' queries require at least one value.'; return false; } diff --git a/tests/Database/Validator/DocumentsQueriesTest.php b/tests/Database/Validator/DocumentsQueriesTest.php index 559206a70..64b5cf0a4 100644 --- a/tests/Database/Validator/DocumentsQueriesTest.php +++ b/tests/Database/Validator/DocumentsQueriesTest.php @@ -66,6 +66,16 @@ public function setUp(): void 'signed' => true, 'array' => false, 'filters' => [], + ]), + new Document([ + '$id' => 'is_bool', + 'key' => 'is_bool', + 'type' => Database::VAR_BOOLEAN, + 'size' => 0, + 'required' => false, + 'signed' => false, + 'array' => false, + 'filters' => [], ]) ], 'indexes' => [ @@ -128,13 +138,8 @@ public function testValidQueries(): void $queries[] = Query::orderDesc(''); $this->assertEquals(true, $validator->isValid($queries)); - $queries = ['search("description", "iron")']; - $this->assertFalse($validator->isValid($queries)); - $this->assertEquals('Searching by attribute "description" requires a fulltext index.', $validator->getDescription()); - - $queries = ['equal("not_found", 4)']; - $this->assertEquals(false, $validator->isValid($queries)); - $this->assertEquals('Query not valid: Attribute not found in schema: not_found', $validator->getDescription()); + $queries = ['equal("is_bool", false)']; + $this->assertEquals(true, $validator->isValid($queries)); } /** @@ -145,7 +150,16 @@ public function testInvalidQueries(): void $validator = new DocumentsQueries($this->collection['attributes'], $this->collection['indexes']); $queries = ['search("description", "iron")']; - $this->assertFalse($validator->isValid($queries)); + $this->assertEquals(false, $validator->isValid($queries)); + $this->assertEquals('Searching by attribute "description" requires a fulltext index.', $validator->getDescription()); + + + $queries = ['equal("not_found", 4)']; + $this->assertEquals(false, $validator->isValid($queries)); + $this->assertEquals('Query not valid: Attribute not found in schema: not_found', $validator->getDescription()); + + $queries = ['search("description", "iron")']; + $this->assertEquals(false, $validator->isValid($queries)); $this->assertEquals('Searching by attribute "description" requires a fulltext index.', $validator->getDescription()); $queries = ['equal("not_found", 4)']; From 959bce0d87dc47e41c970964c68bb756dacc33fc Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 2 May 2023 19:55:47 +0300 Subject: [PATCH 30/75] Strict not empty --- src/Database/Validator/Query/Filter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index 572ebb3f8..526ae4870 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -140,7 +140,7 @@ public function isValid($query): bool case Query::TYPE_CONTAINS: // todo: What to do about unsupported operators? $values = $query->getValues(); - if (is_null($values) || (is_array($values) && isset($values[0]) && empty($values[0]) && $values[0] !== false)) { + if ((empty($values) && $values !== false) || (is_array($values) && isset($values[0]) && empty($values[0]) && $values[0] !== false)) { $this->message = $method . ' queries require at least one value.'; return false; } From 66df107b07ad7c604dc882a9c1e774c4febc9743 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 3 May 2023 11:40:26 +0300 Subject: [PATCH 31/75] Fix empty check --- src/Database/Validator/Query/Filter.php | 2 +- tests/Database/Validator/DocumentsQueriesTest.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index 526ae4870..2d9db1a55 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -140,7 +140,7 @@ public function isValid($query): bool case Query::TYPE_CONTAINS: // todo: What to do about unsupported operators? $values = $query->getValues(); - if ((empty($values) && $values !== false) || (is_array($values) && isset($values[0]) && empty($values[0]) && $values[0] !== false)) { + if (is_null($values) || (is_array($values) && count($values) === 0) || (is_array($values[0]) && count($values[0]) === 0)) { $this->message = $method . ' queries require at least one value.'; return false; } diff --git a/tests/Database/Validator/DocumentsQueriesTest.php b/tests/Database/Validator/DocumentsQueriesTest.php index 64b5cf0a4..94c4d773b 100644 --- a/tests/Database/Validator/DocumentsQueriesTest.php +++ b/tests/Database/Validator/DocumentsQueriesTest.php @@ -118,9 +118,11 @@ public function testValidQueries(): void $queries = [ 'notEqual("title", ["Iron Man", "Ant Man"])', 'equal("description", "Best movie ever")', + 'equal("description", [""])', 'lessThanEqual("price", 6.50)', 'lessThan("price", 6.50)', 'greaterThan("rating", 4)', + 'greaterThan("rating", 0)', 'greaterThanEqual("rating", 6)', 'between("price", 1.50, 6.50)', 'search("title", "SEO")', From 0401b24ea4ea3fd7d3d191db150af262e1324f63 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 3 May 2023 11:53:15 +0300 Subject: [PATCH 32/75] remove comment --- tests/Database/Adapter/MariaDBTest.php | 1 - tests/Database/Adapter/MongoDBTest.php | 203 +++++++++--------- tests/Database/Adapter/MySQLTest.php | 151 +++++++------ tests/Database/Adapter/PostgresTest.php | 125 ++++++----- tests/Database/Adapter/SQLiteTest.php | 145 +++++++------ .../Validator/DocumentQueriesTest.php | 1 - 6 files changed, 310 insertions(+), 316 deletions(-) diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php index 0ceaca1c8..9cc3fc781 100644 --- a/tests/Database/Adapter/MariaDBTest.php +++ b/tests/Database/Adapter/MariaDBTest.php @@ -1,6 +1,5 @@ connect('redis', 6379); -// $redis->flushAll(); -// $cache = new Cache(new RedisAdapter($redis)); -// -// $schema = 'utopiaTests'; // same as $this->testDatabase -// $client = new Client( -// $schema, -// 'mongo', -// 27017, -// 'root', -// 'example', -// false -// ); -// -// $database = new Database(new Mongo($client), $cache); -// $database->setDefaultDatabase($schema); -// $database->setNamespace('myapp_' . uniqid()); -// -// if ($database->exists('utopiaTests')) { -// $database->delete('utopiaTests'); -// } -// -// $database->create(); -// -// return self::$database = $database; -// } -// -// /** -// * @throws Exception -// */ -// public function testCreateExistsDelete(): void -// { -// // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. -// $this->assertNotNull(static::getDatabase()->create()); -// $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); -// $this->assertEquals(true, static::getDatabase()->create()); -// $this->assertEquals(true, static::getDatabase()->setDefaultDatabase($this->testDatabase)); -// } -// -// public function testRenameAttribute(): void -// { -// $this->assertTrue(true); -// } -// -// public function testRenameAttributeExisting(): void -// { -// $this->assertTrue(true); -// } -// -// public function testUpdateAttributeStructure(): void -// { -// $this->assertTrue(true); -// } -// -// public function testKeywords(): void -// { -// $this->assertTrue(true); -// } -// -// public static function killDatabase(): void -// { -// self::$database = null; -// } -//} +namespace Utopia\Tests\Adapter; + +use Exception; +use Redis; +use Utopia\Cache\Cache; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Database\Adapter\Mongo; +use Utopia\Database\Database; +use Utopia\Mongo\Client; +use Utopia\Tests\Base; + +class MongoDBTest extends Base +{ + public static ?Database $database = null; + + + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "mongodb"; + } + + /** + * @return Database + * @throws Exception + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + $cache = new Cache(new RedisAdapter($redis)); + + $schema = 'utopiaTests'; // same as $this->testDatabase + $client = new Client( + $schema, + 'mongo', + 27017, + 'root', + 'example', + false + ); + + $database = new Database(new Mongo($client), $cache); + $database->setDefaultDatabase($schema); + $database->setNamespace('myapp_' . uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + + $database->create(); + + return self::$database = $database; + } + + /** + * @throws Exception + */ + public function testCreateExistsDelete(): void + { + // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. + $this->assertNotNull(static::getDatabase()->create()); + $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); + $this->assertEquals(true, static::getDatabase()->create()); + $this->assertEquals(true, static::getDatabase()->setDefaultDatabase($this->testDatabase)); + } + + public function testRenameAttribute(): void + { + $this->assertTrue(true); + } + + public function testRenameAttributeExisting(): void + { + $this->assertTrue(true); + } + + public function testUpdateAttributeStructure(): void + { + $this->assertTrue(true); + } + + public function testKeywords(): void + { + $this->assertTrue(true); + } + + public static function killDatabase(): void + { + self::$database = null; + } +} diff --git a/tests/Database/Adapter/MySQLTest.php b/tests/Database/Adapter/MySQLTest.php index accd89fa5..f0ee5822b 100644 --- a/tests/Database/Adapter/MySQLTest.php +++ b/tests/Database/Adapter/MySQLTest.php @@ -1,78 +1,77 @@ connect('redis', 6379); -// $redis->flushAll(); -// -// $cache = new Cache(new RedisAdapter($redis)); -// -// $database = new Database(new MySQL($pdo), $cache); -// $database->setDefaultDatabase('utopiaTests'); -// $database->setNamespace('myapp_'.uniqid()); -// -// if ($database->exists('utopiaTests')) { -// $database->delete('utopiaTests'); -// } -// -// $database->create(); -// -// return self::$database = $database; -// } -// -// public static function killDatabase(): void -// { -// self::$database = null; -// } -//} +namespace Utopia\Tests\Adapter; + +use PDO; +use Redis; +use Utopia\Cache\Cache; +use Utopia\Database\Database; +use Utopia\Database\Adapter\MySQL; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Tests\Base; + +class MySQLTest extends Base +{ + public static ?Database $database = null; + + // TODO@kodumbeats hacky way to identify adapters for tests + // Remove once all methods are implemented + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "mysql"; + } + + /** + * + * @return int + */ + public static function getUsedIndexes(): int + { + return MySQL::getCountOfDefaultIndexes(); + } + + /** + * @return Database + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $dbHost = 'mysql'; + $dbPort = '3307'; + $dbUser = 'root'; + $dbPass = 'password'; + + $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MySQL::getPDOAttributes()); + + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + + $cache = new Cache(new RedisAdapter($redis)); + + $database = new Database(new MySQL($pdo), $cache); + $database->setDefaultDatabase('utopiaTests'); + $database->setNamespace('myapp_'.uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + + $database->create(); + + return self::$database = $database; + } + + public static function killDatabase(): void + { + self::$database = null; + } +} diff --git a/tests/Database/Adapter/PostgresTest.php b/tests/Database/Adapter/PostgresTest.php index 7c9aa1724..4b5451341 100644 --- a/tests/Database/Adapter/PostgresTest.php +++ b/tests/Database/Adapter/PostgresTest.php @@ -1,65 +1,64 @@ connect('redis', 6379); -// $redis->flushAll(); -// $cache = new Cache(new RedisAdapter($redis)); -// -// $database = new Database(new Postgres($pdo), $cache); -// $database->setDefaultDatabase('utopiaTests'); -// $database->setNamespace('myapp_'.uniqid()); -// -// if ($database->exists('utopiaTests')) { -// $database->delete('utopiaTests'); -// } -// -// $database->create(); -// -// return self::$database = $database; -// } -// -// public static function killDatabase(): void -// { -// self::$database = null; -// } -//} +namespace Utopia\Tests\Adapter; + +use PDO; +use Redis; +use Utopia\Database\Database; +use Utopia\Database\Adapter\Postgres; +use Utopia\Cache\Cache; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Tests\Base; + +class PostgresTest extends Base +{ + public static ?Database $database = null; + + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "postgres"; + } + + /** + * @reture Adapter + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $dbHost = 'postgres'; + $dbPort = '5432'; + $dbUser = 'root'; + $dbPass = 'password'; + + $pdo = new PDO("pgsql:host={$dbHost};port={$dbPort};", $dbUser, $dbPass, Postgres::getPDOAttributes()); + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + $cache = new Cache(new RedisAdapter($redis)); + + $database = new Database(new Postgres($pdo), $cache); + $database->setDefaultDatabase('utopiaTests'); + $database->setNamespace('myapp_'.uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + + $database->create(); + + return self::$database = $database; + } + + public static function killDatabase(): void + { + self::$database = null; + } +} diff --git a/tests/Database/Adapter/SQLiteTest.php b/tests/Database/Adapter/SQLiteTest.php index b2e2d54dd..b704d58e1 100644 --- a/tests/Database/Adapter/SQLiteTest.php +++ b/tests/Database/Adapter/SQLiteTest.php @@ -1,75 +1,74 @@ connect('redis', 6379); -// $redis->flushAll(); -// -// $cache = new Cache(new RedisAdapter($redis)); -// -// $database = new Database(new SQLite($pdo), $cache); -// $database->setDefaultDatabase('utopiaTests'); -// $database->setNamespace('myapp_'.uniqid()); -// -// return self::$database = $database; -// } -// -// public static function killDatabase(): void -// { -// self::$database = null; -// } -//} +namespace Utopia\Tests\Adapter; + +use PDO; +use Redis; +use Utopia\Cache\Cache; +use Utopia\Database\Database; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Database\Adapter\SQLite; +use Utopia\Tests\Base; + +class SQLiteTest extends Base +{ + public static ?Database $database = null; + + // TODO@kodumbeats hacky way to identify adapters for tests + // Remove once all methods are implemented + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "sqlite"; + } + + /** + * + * @return int + */ + public static function getUsedIndexes(): int + { + return SQLite::getCountOfDefaultIndexes(); + } + + /** + * @return Database + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $sqliteDir = __DIR__."/database.sql"; + + if (file_exists($sqliteDir)) { + unlink($sqliteDir); + } + + $dsn = $sqliteDir; + $dsn = 'memory'; // Overwrite for fast tests + $pdo = new PDO("sqlite:" . $dsn, null, null, SQLite::getPDOAttributes()); + + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + + $cache = new Cache(new RedisAdapter($redis)); + + $database = new Database(new SQLite($pdo), $cache); + $database->setDefaultDatabase('utopiaTests'); + $database->setNamespace('myapp_'.uniqid()); + + return self::$database = $database; + } + + public static function killDatabase(): void + { + self::$database = null; + } +} diff --git a/tests/Database/Validator/DocumentQueriesTest.php b/tests/Database/Validator/DocumentQueriesTest.php index efd9f27fa..aac1c235d 100644 --- a/tests/Database/Validator/DocumentQueriesTest.php +++ b/tests/Database/Validator/DocumentQueriesTest.php @@ -78,7 +78,6 @@ public function testValidQueries(): void public function testInvalidQueries(): void { $validator = new DocumentQueries($this->collection['attributes']); - // $queries = [Query::select(['movies.price'])]; $queries = [Query::limit(1)]; // We only accept Select queries $this->assertEquals(false, $validator->isValid($queries)); From 8ec2ce7af3e7d405303060e30c42587d70e014a1 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 3 May 2023 12:40:48 +0300 Subject: [PATCH 33/75] Fix empty queries --- src/Database/Validator/Query/Filter.php | 2 +- tests/Database/Validator/QueryTest.php | 32 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index 2d9db1a55..a2ee54958 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -140,7 +140,7 @@ public function isValid($query): bool case Query::TYPE_CONTAINS: // todo: What to do about unsupported operators? $values = $query->getValues(); - if (is_null($values) || (is_array($values) && count($values) === 0) || (is_array($values[0]) && count($values[0]) === 0)) { + if ((is_array($values) && count($values) === 0) || (is_array($values[0]) && count($values[0]) === 0)) { $this->message = $method . ' queries require at least one value.'; return false; } diff --git a/tests/Database/Validator/QueryTest.php b/tests/Database/Validator/QueryTest.php index 0ca58e350..3bfded224 100644 --- a/tests/Database/Validator/QueryTest.php +++ b/tests/Database/Validator/QueryTest.php @@ -277,4 +277,36 @@ public function testQueryGetByType(): void } } + /** + * @throws Exception + */ + public function testQueryEmpty(): void + { + $validator = new DocumentsQueries($this->attributes, []); + + $response = $validator->isValid([Query::equal('title', [''])]); + $this->assertEquals(true, $response); + + $response = $validator->isValid([Query::equal('published', [false])]); + $this->assertEquals(true, $response); + + $response = $validator->isValid([Query::equal('price', [0])]); + $this->assertEquals(true, $response); + + $response = $validator->isValid([Query::greaterThan('price', 0)]); + $this->assertEquals(true, $response); + + $response = $validator->isValid([Query::greaterThan('published', false)]); + $this->assertEquals(true, $response); + + $response = $validator->isValid([Query::equal('price', [])]); + $this->assertEquals(false, $response); + + $response = $validator->isValid([Query::greaterThan('price', null)]); + $this->assertEquals(false, $response); + + $response = $validator->isValid([Query::isNull('price')]); + $this->assertEquals(true, $response); + } + } From 46de56c58555c5740ad79bef0467c7451a7a5bc7 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 3 May 2023 12:50:09 +0300 Subject: [PATCH 34/75] formatting --- src/Database/Query.php | 2 +- src/Database/Validator/Query/Filter.php | 1 - src/Database/Validator/Query/Select.php | 3 +-- tests/Database/Base.php | 2 -- tests/Database/Validator/QueryTest.php | 3 +-- 5 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Database/Query.php b/src/Database/Query.php index 0cfea1309..8a43df1e9 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -698,7 +698,7 @@ public static function endsWith(string $attribute, string $value): self * Filters $queries for $types * * @param array $queries - * @param array $types + * @param array $types * @return array */ public static function getByType(array $queries, array $types): array diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index a2ee54958..ac665db08 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -39,7 +39,6 @@ public function __construct(array $attributes = [], int $maxValuesCount = 100) protected function isValidAttribute(string $attribute): bool { if (\str_contains($attribute, '.')) { - // Check for special symbol `.` if (isset($this->schema[$attribute])) { return true; diff --git a/src/Database/Validator/Query/Select.php b/src/Database/Validator/Query/Select.php index ec55962bb..436ea4178 100644 --- a/src/Database/Validator/Query/Select.php +++ b/src/Database/Validator/Query/Select.php @@ -42,9 +42,8 @@ public function isValid($query): bool foreach ($query->getValues() as $attribute) { if (\str_contains($attribute, '.')) { - //special symbols with `dots` - if(isset($this->schema[$attribute])){ + if (isset($this->schema[$attribute])) { continue; } diff --git a/tests/Database/Base.php b/tests/Database/Base.php index fee5cf05f..8f721da91 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -10266,7 +10266,6 @@ public function testSymbolDots(): void // static::getDatabase()->find('dots', [ // Query::select(['dots.name']), // ]); - } public function testManyToOneRelationshipKeyWithSymbols(): void @@ -10307,7 +10306,6 @@ public function testManyToOneRelationshipKeyWithSymbols(): void $this->assertEquals($doc2->getId(), $doc1->getAttribute('$symbols_coll.ection5')[0]->getId()); $this->assertEquals($doc1->getId(), $doc2->getAttribute('$symbols_coll.ection6')->getId()); - } public function testManyToManyRelationshipKeyWithSymbols(): void diff --git a/tests/Database/Validator/QueryTest.php b/tests/Database/Validator/QueryTest.php index 3bfded224..d5bb0b8c0 100644 --- a/tests/Database/Validator/QueryTest.php +++ b/tests/Database/Validator/QueryTest.php @@ -272,7 +272,7 @@ public function testQueryGetByType(): void $queries = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); $this->assertCount(2, $queries); - foreach ($queries as $query){ + foreach ($queries as $query) { $this->assertEquals(true, in_array($query->getMethod(), [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE])); } } @@ -308,5 +308,4 @@ public function testQueryEmpty(): void $response = $validator->isValid([Query::isNull('price')]); $this->assertEquals(true, $response); } - } From 3af2d461c4436e5de8a3911a9f07b6ced8d41801 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 3 May 2023 17:57:17 +0300 Subject: [PATCH 35/75] dots test --- src/Database/Adapter/MariaDB.php | 5 +- src/Database/Adapter/Postgres.php | 5 +- tests/Database/Adapter/MongoDBTest.php | 204 ++++++++++++------------ tests/Database/Adapter/MySQLTest.php | 152 +++++++++--------- tests/Database/Adapter/PostgresTest.php | 126 +++++++-------- tests/Database/Adapter/SQLiteTest.php | 146 ++++++++--------- tests/Database/Base.php | 84 +++++++--- 7 files changed, 386 insertions(+), 336 deletions(-) diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 6ea81b1c3..7a279127e 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1177,6 +1177,7 @@ public function sum(string $collection, string $attribute, array $queries = [], * @param array $selections * @param string $prefix * @return mixed + * @throws Exception */ protected function getAttributeProjection(array $selections, string $prefix = ''): mixed { @@ -1195,11 +1196,11 @@ protected function getAttributeProjection(array $selections, string $prefix = '' if (!empty($prefix)) { foreach ($selections as &$selection) { - $selection = "`{$prefix}`.`{$selection}`"; + $selection = "`{$prefix}`.`{$this->filter($selection)}`"; } } else { foreach ($selections as &$selection) { - $selection = "`{$selection}`"; + $selection = "`{$this->filter($selection)}`"; } } diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 7a9a20ebd..aaa852039 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1184,6 +1184,7 @@ public function sum(string $collection, string $attribute, array $queries = [], * @param string[] $selections * @param string $prefix * @return string + * @throws Exception */ protected function getAttributeProjection(array $selections, string $prefix = ''): string { @@ -1202,11 +1203,11 @@ protected function getAttributeProjection(array $selections, string $prefix = '' if (!empty($prefix)) { foreach ($selections as &$selection) { - $selection = "\"{$prefix}\".\"{$selection}\""; + $selection = "\"{$prefix}\".\"{$this->filter($selection)}\""; } } else { foreach ($selections as &$selection) { - $selection = "\"{$selection}\""; + $selection = "\"{$this->filter($selection)}\""; } } diff --git a/tests/Database/Adapter/MongoDBTest.php b/tests/Database/Adapter/MongoDBTest.php index 563837006..646318a71 100644 --- a/tests/Database/Adapter/MongoDBTest.php +++ b/tests/Database/Adapter/MongoDBTest.php @@ -1,103 +1,103 @@ connect('redis', 6379); - $redis->flushAll(); - $cache = new Cache(new RedisAdapter($redis)); - - $schema = 'utopiaTests'; // same as $this->testDatabase - $client = new Client( - $schema, - 'mongo', - 27017, - 'root', - 'example', - false - ); - - $database = new Database(new Mongo($client), $cache); - $database->setDefaultDatabase($schema); - $database->setNamespace('myapp_' . uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } - - /** - * @throws Exception - */ - public function testCreateExistsDelete(): void - { - // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. - $this->assertNotNull(static::getDatabase()->create()); - $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); - $this->assertEquals(true, static::getDatabase()->create()); - $this->assertEquals(true, static::getDatabase()->setDefaultDatabase($this->testDatabase)); - } - - public function testRenameAttribute(): void - { - $this->assertTrue(true); - } - - public function testRenameAttributeExisting(): void - { - $this->assertTrue(true); - } - - public function testUpdateAttributeStructure(): void - { - $this->assertTrue(true); - } - - public function testKeywords(): void - { - $this->assertTrue(true); - } - - public static function killDatabase(): void - { - self::$database = null; - } -} +// +//namespace Utopia\Tests\Adapter; +// +//use Exception; +//use Redis; +//use Utopia\Cache\Cache; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Database\Adapter\Mongo; +//use Utopia\Database\Database; +//use Utopia\Mongo\Client; +//use Utopia\Tests\Base; +// +//class MongoDBTest extends Base +//{ +// public static ?Database $database = null; +// +// +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "mongodb"; +// } +// +// /** +// * @return Database +// * @throws Exception +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// $cache = new Cache(new RedisAdapter($redis)); +// +// $schema = 'utopiaTests'; // same as $this->testDatabase +// $client = new Client( +// $schema, +// 'mongo', +// 27017, +// 'root', +// 'example', +// false +// ); +// +// $database = new Database(new Mongo($client), $cache); +// $database->setDefaultDatabase($schema); +// $database->setNamespace('myapp_' . uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +// +// /** +// * @throws Exception +// */ +// public function testCreateExistsDelete(): void +// { +// // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. +// $this->assertNotNull(static::getDatabase()->create()); +// $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); +// $this->assertEquals(true, static::getDatabase()->create()); +// $this->assertEquals(true, static::getDatabase()->setDefaultDatabase($this->testDatabase)); +// } +// +// public function testRenameAttribute(): void +// { +// $this->assertTrue(true); +// } +// +// public function testRenameAttributeExisting(): void +// { +// $this->assertTrue(true); +// } +// +// public function testUpdateAttributeStructure(): void +// { +// $this->assertTrue(true); +// } +// +// public function testKeywords(): void +// { +// $this->assertTrue(true); +// } +// +// public static function killDatabase(): void +// { +// self::$database = null; +// } +//} diff --git a/tests/Database/Adapter/MySQLTest.php b/tests/Database/Adapter/MySQLTest.php index f0ee5822b..fd5cf3232 100644 --- a/tests/Database/Adapter/MySQLTest.php +++ b/tests/Database/Adapter/MySQLTest.php @@ -1,77 +1,77 @@ connect('redis', 6379); - $redis->flushAll(); - - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new MySQL($pdo), $cache); - $database->setDefaultDatabase('utopiaTests'); - $database->setNamespace('myapp_'.uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } - - public static function killDatabase(): void - { - self::$database = null; - } -} +// +//namespace Utopia\Tests\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Cache\Cache; +//use Utopia\Database\Database; +//use Utopia\Database\Adapter\MySQL; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Tests\Base; +// +//class MySQLTest extends Base +//{ +// public static ?Database $database = null; +// +// // TODO@kodumbeats hacky way to identify adapters for tests +// // Remove once all methods are implemented +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "mysql"; +// } +// +// /** +// * +// * @return int +// */ +// public static function getUsedIndexes(): int +// { +// return MySQL::getCountOfDefaultIndexes(); +// } +// +// /** +// * @return Database +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $dbHost = 'mysql'; +// $dbPort = '3307'; +// $dbUser = 'root'; +// $dbPass = 'password'; +// +// $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MySQL::getPDOAttributes()); +// +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new MySQL($pdo), $cache); +// $database->setDefaultDatabase('utopiaTests'); +// $database->setNamespace('myapp_'.uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +// +// public static function killDatabase(): void +// { +// self::$database = null; +// } +//} diff --git a/tests/Database/Adapter/PostgresTest.php b/tests/Database/Adapter/PostgresTest.php index 4b5451341..a635ef7f0 100644 --- a/tests/Database/Adapter/PostgresTest.php +++ b/tests/Database/Adapter/PostgresTest.php @@ -1,64 +1,64 @@ connect('redis', 6379); - $redis->flushAll(); - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new Postgres($pdo), $cache); - $database->setDefaultDatabase('utopiaTests'); - $database->setNamespace('myapp_'.uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } - - public static function killDatabase(): void - { - self::$database = null; - } -} +// +//namespace Utopia\Tests\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Database\Database; +//use Utopia\Database\Adapter\Postgres; +//use Utopia\Cache\Cache; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Tests\Base; +// +//class PostgresTest extends Base +//{ +// public static ?Database $database = null; +// +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "postgres"; +// } +// +// /** +// * @reture Adapter +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $dbHost = 'postgres'; +// $dbPort = '5432'; +// $dbUser = 'root'; +// $dbPass = 'password'; +// +// $pdo = new PDO("pgsql:host={$dbHost};port={$dbPort};", $dbUser, $dbPass, Postgres::getPDOAttributes()); +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new Postgres($pdo), $cache); +// $database->setDefaultDatabase('utopiaTests'); +// $database->setNamespace('myapp_'.uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +// +// public static function killDatabase(): void +// { +// self::$database = null; +// } +//} diff --git a/tests/Database/Adapter/SQLiteTest.php b/tests/Database/Adapter/SQLiteTest.php index b704d58e1..7afc3b63d 100644 --- a/tests/Database/Adapter/SQLiteTest.php +++ b/tests/Database/Adapter/SQLiteTest.php @@ -1,74 +1,74 @@ connect('redis', 6379); - $redis->flushAll(); - - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new SQLite($pdo), $cache); - $database->setDefaultDatabase('utopiaTests'); - $database->setNamespace('myapp_'.uniqid()); - - return self::$database = $database; - } - - public static function killDatabase(): void - { - self::$database = null; - } -} +// +//namespace Utopia\Tests\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Cache\Cache; +//use Utopia\Database\Database; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Database\Adapter\SQLite; +//use Utopia\Tests\Base; +// +//class SQLiteTest extends Base +//{ +// public static ?Database $database = null; +// +// // TODO@kodumbeats hacky way to identify adapters for tests +// // Remove once all methods are implemented +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "sqlite"; +// } +// +// /** +// * +// * @return int +// */ +// public static function getUsedIndexes(): int +// { +// return SQLite::getCountOfDefaultIndexes(); +// } +// +// /** +// * @return Database +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $sqliteDir = __DIR__."/database.sql"; +// +// if (file_exists($sqliteDir)) { +// unlink($sqliteDir); +// } +// +// $dsn = $sqliteDir; +// $dsn = 'memory'; // Overwrite for fast tests +// $pdo = new PDO("sqlite:" . $dsn, null, null, SQLite::getPDOAttributes()); +// +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new SQLite($pdo), $cache); +// $database->setDefaultDatabase('utopiaTests'); +// $database->setNamespace('myapp_'.uniqid()); +// +// return self::$database = $database; +// } +// +// public static function killDatabase(): void +// { +// self::$database = null; +// } +//} diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 8f721da91..86fffe541 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -77,6 +77,72 @@ public function testCreateExistsDelete(): void $this->assertEquals(true, static::getDatabase()->create()); } + public function testSymbolDots(): void + { + static::getDatabase()->createCollection('dots.father'); + + $this->assertTrue(static::getDatabase()->createAttribute( + collection: 'dots.father', + id: 'dots.name', + type: Database::VAR_STRING, + size: 255, + required: false + )); + + $document = static::getDatabase()->find('dots.father', [ + Query::select(['dots.name']), + ]); + $this->assertEmpty($document); + + static::getDatabase()->createCollection('dots'); + + $this->assertTrue(static::getDatabase()->createAttribute( + collection: 'dots', + id: 'name', + type: Database::VAR_STRING, + size: 255, + required: false + )); + + static::getDatabase()->createRelationship( + collection: 'dots.father', + relatedCollection: 'dots', + type: Database::RELATION_ONE_TO_ONE + ); + + static::getDatabase()->createDocument('dots.father', new Document([ + '$id' => ID::custom('father'), + 'dots.name' => 'shmuel', + '$permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'dots' => [ + '$id' => ID::custom('child'), + '$permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ] + ])); + + $documents = static::getDatabase()->find('dots.father', [ + Query::select(['*']), + ]); + + $this->assertEquals('shmuel', $documents[0]['dots.name']); + + $documents = static::getDatabase()->find('dots.father', [ + Query::select(['dots.name']) + ]); + + $this->assertEquals('shmuel', $documents[0]['dots.name']); + } + public function testCreatedAtUpdatedAt(): void { $this->assertInstanceOf('Utopia\Database\Document', static::getDatabase()->createCollection('created_at')); @@ -10250,24 +10316,6 @@ public function testOneToManyRelationshipKeyWithSymbols(): void $this->assertEquals($doc1->getId(), $doc2->getAttribute('$symbols_coll.ection4')[0]->getId()); } - public function testSymbolDots(): void - { - static::getDatabase()->createCollection('dots'); - - $this->assertTrue(static::getDatabase()->createAttribute( - collection: 'dots', - id: 'dots.name', - type: Database::VAR_STRING, - size: 255, - required: false - )); - - // todo: why is this failing -// static::getDatabase()->find('dots', [ -// Query::select(['dots.name']), -// ]); - } - public function testManyToOneRelationshipKeyWithSymbols(): void { if (!static::getDatabase()->getAdapter()->getSupportForRelationships()) { From 283dfb5dc2353c3490239e0eeda34eb8ff35d80f Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 4 May 2023 11:13:35 +0300 Subject: [PATCH 36/75] remove NESTING_QUERIES --- src/Database/Validator/Query/Filter.php | 2 +- tests/Database/Base.php | 135 ++++++++++++------------ 2 files changed, 69 insertions(+), 68 deletions(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index ac665db08..4095a21d6 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -48,7 +48,7 @@ protected function isValidAttribute(string $attribute): bool // Utopia will validate each nested level during the recursive calls. $attribute = \explode('.', $attribute)[0]; - if (!Filter::$NESTING_QUERIES && isset($this->schema[$attribute])) { + if (isset($this->schema[$attribute])) { $this->message = 'Cannot query nested attribute on: ' . $attribute; return false; } diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 86fffe541..1fc682396 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -43,7 +43,6 @@ abstract protected static function getAdapterName(): string; public function setUp(): void { Authorization::setRole('any'); - Filter::$NESTING_QUERIES = true; } public function tearDown(): void @@ -77,72 +76,6 @@ public function testCreateExistsDelete(): void $this->assertEquals(true, static::getDatabase()->create()); } - public function testSymbolDots(): void - { - static::getDatabase()->createCollection('dots.father'); - - $this->assertTrue(static::getDatabase()->createAttribute( - collection: 'dots.father', - id: 'dots.name', - type: Database::VAR_STRING, - size: 255, - required: false - )); - - $document = static::getDatabase()->find('dots.father', [ - Query::select(['dots.name']), - ]); - $this->assertEmpty($document); - - static::getDatabase()->createCollection('dots'); - - $this->assertTrue(static::getDatabase()->createAttribute( - collection: 'dots', - id: 'name', - type: Database::VAR_STRING, - size: 255, - required: false - )); - - static::getDatabase()->createRelationship( - collection: 'dots.father', - relatedCollection: 'dots', - type: Database::RELATION_ONE_TO_ONE - ); - - static::getDatabase()->createDocument('dots.father', new Document([ - '$id' => ID::custom('father'), - 'dots.name' => 'shmuel', - '$permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'dots' => [ - '$id' => ID::custom('child'), - '$permissions' => [ - Permission::read(Role::any()), - Permission::create(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - ] - ])); - - $documents = static::getDatabase()->find('dots.father', [ - Query::select(['*']), - ]); - - $this->assertEquals('shmuel', $documents[0]['dots.name']); - - $documents = static::getDatabase()->find('dots.father', [ - Query::select(['dots.name']) - ]); - - $this->assertEquals('shmuel', $documents[0]['dots.name']); - } - public function testCreatedAtUpdatedAt(): void { $this->assertInstanceOf('Utopia\Database\Document', static::getDatabase()->createCollection('created_at')); @@ -338,6 +271,74 @@ public function testAttributeKeyWithSymbols(): void $this->assertEquals('value', $document->getAttribute('key_with.sym$bols')); } + public function testSymbolDots(): void + { + static::getDatabase()->createCollection('dots.father'); + + $this->assertTrue(static::getDatabase()->createAttribute( + collection: 'dots.father', + id: 'dots.name', + type: Database::VAR_STRING, + size: 255, + required: false + )); + + $document = static::getDatabase()->find('dots.father', [ + Query::select(['dots.name']), + ]); + $this->assertEmpty($document); + + static::getDatabase()->createCollection('dots'); + + $this->assertTrue(static::getDatabase()->createAttribute( + collection: 'dots', + id: 'name', + type: Database::VAR_STRING, + size: 255, + required: false + )); + + static::getDatabase()->createRelationship( + collection: 'dots.father', + relatedCollection: 'dots', + type: Database::RELATION_ONE_TO_ONE + ); + + static::getDatabase()->createDocument('dots.father', new Document([ + '$id' => ID::custom('father'), + 'dots.name' => 'shmuel', + '$permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'dots' => [ + '$id' => ID::custom('child'), + '$permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ] + ])); + + $documents = static::getDatabase()->find('dots.father', [ + Query::select(['*']), + ]); + + $this->assertEquals('shmuel', $documents[0]['dots.name']); + + //todo: abmigious syntax +// $documents = static::getDatabase()->find('dots.father', [ +// Query::select(['dots.name']) +// ]); +// +// $this->assertEquals('shmuel', $documents[0]['dots.name']); + + } + /** * @depends testAttributeCaseInsensitivity */ From d7795caccab8811ebe0e30d6f425d277ed522ad5 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 4 May 2023 16:23:51 +0300 Subject: [PATCH 37/75] remove nested queries --- tests/Database/Adapter/MongoDBTest.php | 204 +++++++++--------- tests/Database/Adapter/MySQLTest.php | 152 ++++++------- tests/Database/Adapter/PostgresTest.php | 126 +++++------ tests/Database/Adapter/SQLiteTest.php | 146 ++++++------- tests/Database/Base.php | 275 ++---------------------- 5 files changed, 337 insertions(+), 566 deletions(-) diff --git a/tests/Database/Adapter/MongoDBTest.php b/tests/Database/Adapter/MongoDBTest.php index 646318a71..563837006 100644 --- a/tests/Database/Adapter/MongoDBTest.php +++ b/tests/Database/Adapter/MongoDBTest.php @@ -1,103 +1,103 @@ connect('redis', 6379); -// $redis->flushAll(); -// $cache = new Cache(new RedisAdapter($redis)); -// -// $schema = 'utopiaTests'; // same as $this->testDatabase -// $client = new Client( -// $schema, -// 'mongo', -// 27017, -// 'root', -// 'example', -// false -// ); -// -// $database = new Database(new Mongo($client), $cache); -// $database->setDefaultDatabase($schema); -// $database->setNamespace('myapp_' . uniqid()); -// -// if ($database->exists('utopiaTests')) { -// $database->delete('utopiaTests'); -// } -// -// $database->create(); -// -// return self::$database = $database; -// } -// -// /** -// * @throws Exception -// */ -// public function testCreateExistsDelete(): void -// { -// // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. -// $this->assertNotNull(static::getDatabase()->create()); -// $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); -// $this->assertEquals(true, static::getDatabase()->create()); -// $this->assertEquals(true, static::getDatabase()->setDefaultDatabase($this->testDatabase)); -// } -// -// public function testRenameAttribute(): void -// { -// $this->assertTrue(true); -// } -// -// public function testRenameAttributeExisting(): void -// { -// $this->assertTrue(true); -// } -// -// public function testUpdateAttributeStructure(): void -// { -// $this->assertTrue(true); -// } -// -// public function testKeywords(): void -// { -// $this->assertTrue(true); -// } -// -// public static function killDatabase(): void -// { -// self::$database = null; -// } -//} + +namespace Utopia\Tests\Adapter; + +use Exception; +use Redis; +use Utopia\Cache\Cache; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Database\Adapter\Mongo; +use Utopia\Database\Database; +use Utopia\Mongo\Client; +use Utopia\Tests\Base; + +class MongoDBTest extends Base +{ + public static ?Database $database = null; + + + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "mongodb"; + } + + /** + * @return Database + * @throws Exception + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + $cache = new Cache(new RedisAdapter($redis)); + + $schema = 'utopiaTests'; // same as $this->testDatabase + $client = new Client( + $schema, + 'mongo', + 27017, + 'root', + 'example', + false + ); + + $database = new Database(new Mongo($client), $cache); + $database->setDefaultDatabase($schema); + $database->setNamespace('myapp_' . uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + + $database->create(); + + return self::$database = $database; + } + + /** + * @throws Exception + */ + public function testCreateExistsDelete(): void + { + // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. + $this->assertNotNull(static::getDatabase()->create()); + $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); + $this->assertEquals(true, static::getDatabase()->create()); + $this->assertEquals(true, static::getDatabase()->setDefaultDatabase($this->testDatabase)); + } + + public function testRenameAttribute(): void + { + $this->assertTrue(true); + } + + public function testRenameAttributeExisting(): void + { + $this->assertTrue(true); + } + + public function testUpdateAttributeStructure(): void + { + $this->assertTrue(true); + } + + public function testKeywords(): void + { + $this->assertTrue(true); + } + + public static function killDatabase(): void + { + self::$database = null; + } +} diff --git a/tests/Database/Adapter/MySQLTest.php b/tests/Database/Adapter/MySQLTest.php index fd5cf3232..f0ee5822b 100644 --- a/tests/Database/Adapter/MySQLTest.php +++ b/tests/Database/Adapter/MySQLTest.php @@ -1,77 +1,77 @@ connect('redis', 6379); -// $redis->flushAll(); -// -// $cache = new Cache(new RedisAdapter($redis)); -// -// $database = new Database(new MySQL($pdo), $cache); -// $database->setDefaultDatabase('utopiaTests'); -// $database->setNamespace('myapp_'.uniqid()); -// -// if ($database->exists('utopiaTests')) { -// $database->delete('utopiaTests'); -// } -// -// $database->create(); -// -// return self::$database = $database; -// } -// -// public static function killDatabase(): void -// { -// self::$database = null; -// } -//} + +namespace Utopia\Tests\Adapter; + +use PDO; +use Redis; +use Utopia\Cache\Cache; +use Utopia\Database\Database; +use Utopia\Database\Adapter\MySQL; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Tests\Base; + +class MySQLTest extends Base +{ + public static ?Database $database = null; + + // TODO@kodumbeats hacky way to identify adapters for tests + // Remove once all methods are implemented + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "mysql"; + } + + /** + * + * @return int + */ + public static function getUsedIndexes(): int + { + return MySQL::getCountOfDefaultIndexes(); + } + + /** + * @return Database + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $dbHost = 'mysql'; + $dbPort = '3307'; + $dbUser = 'root'; + $dbPass = 'password'; + + $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MySQL::getPDOAttributes()); + + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + + $cache = new Cache(new RedisAdapter($redis)); + + $database = new Database(new MySQL($pdo), $cache); + $database->setDefaultDatabase('utopiaTests'); + $database->setNamespace('myapp_'.uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + + $database->create(); + + return self::$database = $database; + } + + public static function killDatabase(): void + { + self::$database = null; + } +} diff --git a/tests/Database/Adapter/PostgresTest.php b/tests/Database/Adapter/PostgresTest.php index a635ef7f0..4b5451341 100644 --- a/tests/Database/Adapter/PostgresTest.php +++ b/tests/Database/Adapter/PostgresTest.php @@ -1,64 +1,64 @@ connect('redis', 6379); -// $redis->flushAll(); -// $cache = new Cache(new RedisAdapter($redis)); -// -// $database = new Database(new Postgres($pdo), $cache); -// $database->setDefaultDatabase('utopiaTests'); -// $database->setNamespace('myapp_'.uniqid()); -// -// if ($database->exists('utopiaTests')) { -// $database->delete('utopiaTests'); -// } -// -// $database->create(); -// -// return self::$database = $database; -// } -// -// public static function killDatabase(): void -// { -// self::$database = null; -// } -//} + +namespace Utopia\Tests\Adapter; + +use PDO; +use Redis; +use Utopia\Database\Database; +use Utopia\Database\Adapter\Postgres; +use Utopia\Cache\Cache; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Tests\Base; + +class PostgresTest extends Base +{ + public static ?Database $database = null; + + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "postgres"; + } + + /** + * @reture Adapter + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $dbHost = 'postgres'; + $dbPort = '5432'; + $dbUser = 'root'; + $dbPass = 'password'; + + $pdo = new PDO("pgsql:host={$dbHost};port={$dbPort};", $dbUser, $dbPass, Postgres::getPDOAttributes()); + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + $cache = new Cache(new RedisAdapter($redis)); + + $database = new Database(new Postgres($pdo), $cache); + $database->setDefaultDatabase('utopiaTests'); + $database->setNamespace('myapp_'.uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + + $database->create(); + + return self::$database = $database; + } + + public static function killDatabase(): void + { + self::$database = null; + } +} diff --git a/tests/Database/Adapter/SQLiteTest.php b/tests/Database/Adapter/SQLiteTest.php index 7afc3b63d..b704d58e1 100644 --- a/tests/Database/Adapter/SQLiteTest.php +++ b/tests/Database/Adapter/SQLiteTest.php @@ -1,74 +1,74 @@ connect('redis', 6379); -// $redis->flushAll(); -// -// $cache = new Cache(new RedisAdapter($redis)); -// -// $database = new Database(new SQLite($pdo), $cache); -// $database->setDefaultDatabase('utopiaTests'); -// $database->setNamespace('myapp_'.uniqid()); -// -// return self::$database = $database; -// } -// -// public static function killDatabase(): void -// { -// self::$database = null; -// } -//} + +namespace Utopia\Tests\Adapter; + +use PDO; +use Redis; +use Utopia\Cache\Cache; +use Utopia\Database\Database; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Database\Adapter\SQLite; +use Utopia\Tests\Base; + +class SQLiteTest extends Base +{ + public static ?Database $database = null; + + // TODO@kodumbeats hacky way to identify adapters for tests + // Remove once all methods are implemented + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "sqlite"; + } + + /** + * + * @return int + */ + public static function getUsedIndexes(): int + { + return SQLite::getCountOfDefaultIndexes(); + } + + /** + * @return Database + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $sqliteDir = __DIR__."/database.sql"; + + if (file_exists($sqliteDir)) { + unlink($sqliteDir); + } + + $dsn = $sqliteDir; + $dsn = 'memory'; // Overwrite for fast tests + $pdo = new PDO("sqlite:" . $dsn, null, null, SQLite::getPDOAttributes()); + + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + + $cache = new Cache(new RedisAdapter($redis)); + + $database = new Database(new SQLite($pdo), $cache); + $database->setDefaultDatabase('utopiaTests'); + $database->setNamespace('myapp_'.uniqid()); + + return self::$database = $database; + } + + public static function killDatabase(): void + { + self::$database = null; + } +} diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 1fc682396..62a428066 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -336,7 +336,6 @@ public function testSymbolDots(): void // ]); // // $this->assertEquals('shmuel', $documents[0]['dots.name']); - } /** @@ -3938,19 +3937,6 @@ public function testOneToOneOneWayRelationship(): void $library = static::getDatabase()->getDocument('library', 'library2'); $this->assertArrayNotHasKey('person', $library); - // Query related document - $people = static::getDatabase()->find('person', [ - Query::equal('library.name', ['Library 2']) - ]); - - $this->assertEquals(1, \count($people)); - $this->assertEquals( - 'Library 2', - $people[0] - ->getAttribute('library') - ->getAttribute('name') - ); - $people = static::getDatabase()->find('person', [ Query::select(['name']) ]); @@ -4103,13 +4089,6 @@ public function testOneToOneOneWayRelationship(): void $person1->setAttribute('library', $library4) ); - // Query new related document - $people = static::getDatabase()->find('person', [ - Query::equal('library.name', ['Library 4']) - ]); - - $this->assertEquals(1, \count($people)); - // Rename relationship key static::getDatabase()->updateRelationship( collection: 'person', @@ -4432,19 +4411,6 @@ public function testOneToOneTwoWayRelationship(): void $this->assertEquals('city4', $city['$id']); $this->assertArrayNotHasKey('country', $city); - // Query related document - $countries = static::getDatabase()->find('country', [ - Query::equal('city.name', ['Paris']) - ]); - - $this->assertEquals(1, \count($countries)); - $this->assertEquals( - 'Paris', - $countries[0] - ->getAttribute('city') - ->getAttribute('name') - ); - $countries = static::getDatabase()->find('country'); $this->assertEquals(4, \count($countries)); @@ -4637,13 +4603,6 @@ public function testOneToOneTwoWayRelationship(): void $country1->setAttribute('city', $city7) ); - // Query new relationship - $countries = static::getDatabase()->find('country', [ - Query::equal('city.name', ['Copenhagen']) - ]); - - $this->assertEquals(1, \count($countries)); - // Create a new country with no relation static::getDatabase()->createDocument('country', new Document([ '$id' => 'country7', @@ -4662,13 +4621,6 @@ public function testOneToOneTwoWayRelationship(): void $city1->setAttribute('country', 'country7') ); - // Query inverse related document again - $people = static::getDatabase()->find('city', [ - Query::equal('country.name', ['Denmark']) - ]); - - $this->assertEquals(1, \count($people)); - // Rename relationship keys on both sides static::getDatabase()->updateRelationship( 'country', @@ -4920,21 +4872,6 @@ public function testOneToManyOneWayRelationship(): void $album = static::getDatabase()->getDocument('album', 'album2'); $this->assertArrayNotHasKey('artist', $album); - // Query related document - $artists = static::getDatabase()->find('artist', [ - Query::equal('albums.name', ['Album 2']) - ]); - - $this->assertCount(1, $artists); - $this->assertCount(2, $artists[0]['albums']); - - $this->assertEquals( - 'Album 2', - $artists[0] - ->getAttribute('albums')[0] - ->getAttribute('name') - ); - $artists = static::getDatabase()->find('artist'); $this->assertEquals(2, \count($artists)); @@ -5033,10 +4970,14 @@ public function testOneToManyOneWayRelationship(): void $artist1->setAttribute('albums', ['album2']) ); - // Query related document again - $artists = static::getDatabase()->find('artist', [ - Query::equal('albums.name', ['Album 2']) - ]); + $artists = static::getDatabase()->find('artist', []); + $artists = \array_filter( // Instead of using related query + $artists, + fn (Document $el) => \array_filter( + $el->getAttribute('albums'), + fn (Document $el2) => $el2['name'] === 'Album 2' + ) + ); $this->assertEquals(1, \count($artists)); $this->assertEquals(1, \count($artists[0]['albums'])); @@ -5048,10 +4989,14 @@ public function testOneToManyOneWayRelationship(): void $artist1->setAttribute('albums', ['album1', 'album2']) ); - // Query related document again - $artists = static::getDatabase()->find('artist', [ - Query::equal('albums.name', ['Album 2']) - ]); + $artists = static::getDatabase()->find('artist', []); + $artists = \array_filter( // Instead of using related query + $artists, + fn (Document $el) => \array_filter( + $el->getAttribute('albums'), + fn (Document $el2) => $el2['name'] === 'Album 2' + ) + ); $this->assertEquals(1, \count($artists)); $this->assertEquals(2, \count($artists[0]['albums'])); @@ -5377,20 +5322,6 @@ public function testOneToManyTwoWayRelationship(): void $this->assertEquals('customer4', $customer['$id']); $this->assertArrayNotHasKey('accounts', $customer); - // Query related document - $customers = static::getDatabase()->find('customer', [ - Query::equal('accounts.name', ['Account 2']) - ]); - - $this->assertEquals(1, \count($customers)); - - $this->assertEquals( - 'Account 2', - $customers[0] - ->getAttribute('accounts')[0] - ->getAttribute('name') - ); - $customers = static::getDatabase()->find('customer'); $this->assertEquals(4, \count($customers)); @@ -5537,14 +5468,6 @@ public function testOneToManyTwoWayRelationship(): void $customer1->setAttribute('accounts', ['account2']) ); - // Query related document again - $customers = static::getDatabase()->find('customer', [ - Query::equal('accounts.name', ['Account 2 Updated']) - ]); - - $this->assertEquals(1, \count($customers)); - $this->assertEquals(1, \count($customers[0]['accounts'])); - // Update document with new related document static::getDatabase()->updateDocument( 'customer', @@ -5552,13 +5475,13 @@ public function testOneToManyTwoWayRelationship(): void $customer1->setAttribute('accounts', ['account1', 'account2']) ); - // Query related document again - $customers = static::getDatabase()->find('customer', [ - Query::equal('accounts.name', ['Account 2 Updated']) - ]); - - $this->assertEquals(1, \count($customers)); - $this->assertEquals(2, \count($customers[0]['accounts'])); +// // Query related document again +// $customers = static::getDatabase()->find('customer', [ +// Query::equal('accounts.name', ['Account 2 Updated']) +// ]); +// +// $this->assertEquals(1, \count($customers)); +// $this->assertEquals(2, \count($customers[0]['accounts'])); // Update inverse document static::getDatabase()->updateDocument( @@ -5567,21 +5490,6 @@ public function testOneToManyTwoWayRelationship(): void $account2->setAttribute('customer', 'customer2') ); - // Query related document again - $customers = static::getDatabase()->find('customer', [ - Query::equal('accounts.name', ['Account 2 Updated']) - ]); - - $this->assertEquals(1, \count($customers)); - $this->assertEquals(1, \count($customers[0]['accounts'])); - - // Query inverse document again - $customers = static::getDatabase()->find('account', [ - Query::equal('customer.name', ['Customer 2 Updated']) - ]); - - $this->assertEquals(1, \count($customers)); - // Rename relationship keys on both sides static::getDatabase()->updateRelationship( 'customer', @@ -5825,20 +5733,6 @@ public function testManyToOneOneWayRelationship(): void $movie = static::getDatabase()->getDocument('movie', 'movie2'); $this->assertArrayNotHasKey('reviews', $movie); - // Query related document - $reviews = static::getDatabase()->find('review', [ - Query::equal('movie.name', ['Movie 2']) - ]); - - $this->assertEquals(1, \count($reviews)); - - $this->assertEquals( - 'Movie 2', - $reviews[0] - ->getAttribute('movie') - ->getAttribute('name') - ); - $reviews = static::getDatabase()->find('review'); $this->assertEquals(3, \count($reviews)); @@ -5925,13 +5819,6 @@ public function testManyToOneOneWayRelationship(): void $review1->setAttribute('movie', 'movie2') ); - // Query related document again - $reviews = static::getDatabase()->find('review', [ - Query::equal('movie.name', ['Movie 2']) - ]); - - $this->assertEquals(2, \count($reviews)); - // Rename relationship keys on both sides static::getDatabase()->updateRelationship( 'review', @@ -6214,20 +6101,6 @@ public function testManyToOneTwoWayRelationship(): void $this->assertEquals('product4', $products[0]['$id']); $this->assertArrayNotHasKey('store', $products[0]); - // Query related document - $products = static::getDatabase()->find('product', [ - Query::equal('store.name', ['Store 2']) - ]); - - $this->assertEquals(1, \count($products)); - - $this->assertEquals( - 'Store 2', - $products[0] - ->getAttribute('store') - ->getAttribute('name') - ); - $products = static::getDatabase()->find('product'); $this->assertEquals(4, \count($products)); @@ -6371,13 +6244,6 @@ public function testManyToOneTwoWayRelationship(): void $product1->setAttribute('store', 'store2') ); - // Query related document again - $products = static::getDatabase()->find('product', [ - Query::equal('store.name', ['Store 2']) - ]); - - $this->assertEquals(2, \count($products)); - $store1 = static::getDatabase()->getDocument('store', 'store1'); // Update inverse document @@ -6387,21 +6253,6 @@ public function testManyToOneTwoWayRelationship(): void $store1->setAttribute('products', ['product1']) ); - // Query related document again - $stores = static::getDatabase()->find('store', [ - Query::equal('products.name', ['Product 1 Updated']) - ]); - - $this->assertEquals(1, \count($stores)); - $this->assertEquals(1, \count($stores[0]['products'])); - - // Query inverse document again - $products = static::getDatabase()->find('product', [ - Query::equal('store.name', ['Store 2']) - ]); - - $this->assertEquals(1, \count($products)); - $store2 = static::getDatabase()->getDocument('store', 'store2'); // Update inverse document @@ -6411,21 +6262,6 @@ public function testManyToOneTwoWayRelationship(): void $store2->setAttribute('products', ['product1', 'product2']) ); - // Query related document again - $stores = static::getDatabase()->find('store', [ - Query::equal('products.name', ['Product 1 Updated']) - ]); - - $this->assertEquals(1, \count($stores)); - $this->assertEquals(2, \count($stores[0]['products'])); - - // Query inverse document again - $products = static::getDatabase()->find('product', [ - Query::equal('store.name', ['Store 2']) - ]); - - $this->assertEquals(2, \count($products)); - // Rename relationship keys on both sides static::getDatabase()->updateRelationship( 'product', @@ -6640,20 +6476,6 @@ public function testManyToManyOneWayRelationship(): void $library = static::getDatabase()->getDocument('song', 'song2'); $this->assertArrayNotHasKey('songs', $library); - // Query related document - $playlists = static::getDatabase()->find('playlist', [ - Query::equal('songs.name', ['Song 2']) - ]); - - $this->assertEquals(1, \count($playlists)); - - $this->assertEquals( - 'Song 2', - $playlists[0] - ->getAttribute('songs')[0] - ->getAttribute('name') - ); - $playlists = static::getDatabase()->find('playlist'); $this->assertEquals(2, \count($playlists)); @@ -6756,14 +6578,6 @@ public function testManyToManyOneWayRelationship(): void $playlist1->setAttribute('songs', ['song2']) ); - // Query related document again - $playlists = static::getDatabase()->find('playlist', [ - Query::equal('songs.name', ['Song 2']) - ]); - - $this->assertEquals(3, \count($playlists)); - $this->assertEquals(1, \count($playlists[0]['songs'])); - // Rename relationship key static::getDatabase()->updateRelationship( 'playlist', @@ -7041,24 +6855,6 @@ public function testManyToManyTwoWayRelationship(): void $this->assertEquals('student4', $student[0]['$id']); $this->assertArrayNotHasKey('classes', $student[0]); - // Query related document - $students = static::getDatabase()->find('students', [ - Query::equal('classes.name', ['Class 2']) - ]); - - $this->assertEquals(1, \count($students)); - - $this->assertEquals( - 'Class 2', - $students[0] - ->getAttribute('classes')[0] - ->getAttribute('name') - ); - - $students = static::getDatabase()->find('students'); - - $this->assertEquals(4, \count($students)); - // Select related document attributes $student = static::getDatabase()->findOne('students', [ Query::select(['*', 'classes.name']) @@ -7198,15 +6994,6 @@ public function testManyToManyTwoWayRelationship(): void $student1->setAttribute('classes', ['class2']) ); - // Query related document again - $students = static::getDatabase()->find('students', [ - Query::equal('classes.name', ['Class 2 Updated']) - ]); - - $this->assertEquals(2, \count($students)); - $this->assertEquals(1, \count($students[0]['classes'])); - $this->assertEquals(1, \count($students[1]['classes'])); - $class1 = static::getDatabase()->getDocument('classes', 'class1'); // Update inverse document @@ -7216,22 +7003,6 @@ public function testManyToManyTwoWayRelationship(): void $class1->setAttribute('students', ['student1']) ); - // Query related document again - $students = static::getDatabase()->find('students', [ - Query::equal('classes.name', ['Class 2 Updated']) - ]); - - $this->assertEquals(2, \count($students)); - $this->assertEquals(2, \count($students[0]['classes'])); - $this->assertEquals(1, \count($students[1]['classes'])); - - // Query inverse document again - $customers = static::getDatabase()->find('classes', [ - Query::equal('students.name', ['Student 2 Updated']) - ]); - - $this->assertEquals(1, \count($customers)); - // Rename relationship keys on both sides static::getDatabase()->updateRelationship( 'students', From 852fd1ccd8e96a9ab56b6776a9c99a78df00d761 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 4 May 2023 17:36:24 +0300 Subject: [PATCH 38/75] typo --- tests/Database/Base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 62a428066..e2a678ac7 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -330,7 +330,7 @@ public function testSymbolDots(): void $this->assertEquals('shmuel', $documents[0]['dots.name']); - //todo: abmigious syntax + //todo: ambiguous syntax // $documents = static::getDatabase()->find('dots.father', [ // Query::select(['dots.name']) // ]); From 44c5da9f236ca18bfd22eef9e92b8e88939c3383 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 7 May 2023 10:05:12 +0300 Subject: [PATCH 39/75] remove filtering --- tests/Database/Base.php | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index e2a678ac7..93a2dfe38 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -330,7 +330,7 @@ public function testSymbolDots(): void $this->assertEquals('shmuel', $documents[0]['dots.name']); - //todo: ambiguous syntax + //todo: ambiguous syntax for parent attribute VS relation attribute // $documents = static::getDatabase()->find('dots.father', [ // Query::select(['dots.name']) // ]); @@ -4970,18 +4970,6 @@ public function testOneToManyOneWayRelationship(): void $artist1->setAttribute('albums', ['album2']) ); - $artists = static::getDatabase()->find('artist', []); - $artists = \array_filter( // Instead of using related query - $artists, - fn (Document $el) => \array_filter( - $el->getAttribute('albums'), - fn (Document $el2) => $el2['name'] === 'Album 2' - ) - ); - - $this->assertEquals(1, \count($artists)); - $this->assertEquals(1, \count($artists[0]['albums'])); - // Update document with new related documents, will remove existing relations static::getDatabase()->updateDocument( 'artist', @@ -4989,18 +4977,6 @@ public function testOneToManyOneWayRelationship(): void $artist1->setAttribute('albums', ['album1', 'album2']) ); - $artists = static::getDatabase()->find('artist', []); - $artists = \array_filter( // Instead of using related query - $artists, - fn (Document $el) => \array_filter( - $el->getAttribute('albums'), - fn (Document $el2) => $el2['name'] === 'Album 2' - ) - ); - - $this->assertEquals(1, \count($artists)); - $this->assertEquals(2, \count($artists[0]['albums'])); - // Rename relationship key static::getDatabase()->updateRelationship( 'artist', From be26112cf2da2dd4248a0addabf1cdb720dac68f Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 15 May 2023 12:31:59 +0300 Subject: [PATCH 40/75] variadic --- src/Database/Database.php | 4 ++-- src/Database/Validator/IndexedQueries.php | 6 +++--- src/Database/Validator/Queries.php | 4 ++-- src/Database/Validator/Queries/Documents.php | 2 +- tests/Database/Base.php | 2 -- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index bcb9893ba..4a94d8ef2 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -16,7 +16,7 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\Authorization; -use Utopia\Database\Validator\Queries\Documents as DocumentsQueriesValidator; +use Utopia\Database\Validator\Queries\Documents; use Utopia\Database\Validator\Index as IndexValidator; use Utopia\Database\Validator\Permissions; use Utopia\Database\Validator\Structure; @@ -3666,7 +3666,7 @@ public function find(string $collection, array $queries = [], ?int $timeout = nu $attributes = $collection->getAttribute('attributes', []); $indexes = $collection->getAttribute('indexes', []); - $validator = new DocumentsQueriesValidator($attributes, $indexes); + $validator = new Documents($attributes, $indexes); if (!$validator->isValid($queries)) { throw new Exception($validator->getDescription()); } diff --git a/src/Database/Validator/IndexedQueries.php b/src/Database/Validator/IndexedQueries.php index 99fdce0c0..24846460e 100644 --- a/src/Database/Validator/IndexedQueries.php +++ b/src/Database/Validator/IndexedQueries.php @@ -27,10 +27,10 @@ class IndexedQueries extends Queries * * @param array $attributes * @param array $indexes - * @param Base ...$validators + * @param array $validators * @throws Exception */ - public function __construct(array $attributes = [], array $indexes = [], Base ...$validators) + public function __construct(array $attributes = [], array $indexes = [], array $validators = []) { $this->attributes = $attributes; @@ -53,7 +53,7 @@ public function __construct(array $attributes = [], array $indexes = [], Base .. $this->indexes[] = $index; } - parent::__construct(...$validators); + parent::__construct($validators); } /** diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index 558b21ac6..c7bc76f1d 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -21,9 +21,9 @@ class Queries extends Validator /** * Queries constructor * - * @param Base ...$validators a list of validators + * @param array $validators */ - public function __construct(Base ...$validators) + public function __construct(array $validators) { $this->validators = $validators; } diff --git a/src/Database/Validator/Queries/Documents.php b/src/Database/Validator/Queries/Documents.php index 78223e323..742015eb0 100644 --- a/src/Database/Validator/Queries/Documents.php +++ b/src/Database/Validator/Queries/Documents.php @@ -54,6 +54,6 @@ public function __construct(array $attributes, array $indexes) new Select($attributes), ]; - parent::__construct($attributes, $indexes, ...$validators); + parent::__construct($attributes, $indexes, $validators); } } diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 93a2dfe38..9a40e0760 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -902,7 +902,6 @@ public function testGetDocumentSelect(Document $document): Document return $document; } - /** * @depends testCreateDocument */ @@ -913,7 +912,6 @@ public function testFulltextIndexWithInteger(): void static::getDatabase()->createIndex('documents', 'fulltext_integer', Database::INDEX_FULLTEXT, ['string','integer']); } - /** * @depends testCreateDocument */ From abe2d33bb56e796927f2c399b7eb5e636e6c258a Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 15 May 2023 13:43:24 +0300 Subject: [PATCH 41/75] variadic constructor --- .../Database/Validator/IndexedQueriesTest.php | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/tests/Database/Validator/IndexedQueriesTest.php b/tests/Database/Validator/IndexedQueriesTest.php index 18bc115d5..c55aabd5e 100644 --- a/tests/Database/Validator/IndexedQueriesTest.php +++ b/tests/Database/Validator/IndexedQueriesTest.php @@ -2,6 +2,7 @@ namespace Utopia\Tests\Validator; +use Exception; use PHPUnit\Framework\TestCase; use Utopia\Database\Database; use Utopia\Database\Document; @@ -41,13 +42,13 @@ public function testInvalidMethod(): void $validator = new IndexedQueries(); $this->assertEquals(false, $validator->isValid(['equal("attr", "value")'])); - $validator = new IndexedQueries([], [], new Limit()); + $validator = new IndexedQueries([], [], [new Limit()]); $this->assertEquals(false, $validator->isValid(['equal("attr", "value")'])); } public function testInvalidValue(): void { - $validator = new IndexedQueries([], [], new Limit()); + $validator = new IndexedQueries([], [], [new Limit()]); $this->assertEquals(false, $validator->isValid(['limit(-1)'])); } @@ -76,11 +77,13 @@ public function testValid(): void $validator = new IndexedQueries( $attributes, $indexes, - new Cursor(), - new Filter($attributes), - new Limit(), - new Offset(), - new Order($attributes), + [ + 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()); @@ -109,11 +112,13 @@ public function testMissingIndex(): void $validator = new IndexedQueries( $attributes, $indexes, - new Cursor(), - new Filter($attributes), - new Limit(), - new Offset(), - new Order($attributes), + [ + 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()); From a61e77a1ce8c5cb6a7909be2712cde870afd5851 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 15 May 2023 14:20:21 +0300 Subject: [PATCH 42/75] Invalid query: single quote --- src/Database/Validator/Queries.php | 4 ++-- src/Database/Validator/Queries/Document.php | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index c7bc76f1d..bd1903e66 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -47,7 +47,7 @@ public function getDescription(): string public function isValid($value): bool { if (!is_array($value)) { - $this->message = "Queries must be an array"; + $this->message = 'Queries must be an array'; return false; } @@ -56,7 +56,7 @@ public function isValid($value): bool try { $query = Query::parse($query); } catch (\Throwable) { - $this->message = "Invalid query: {$query}"; + $this->message = 'Invalid query: ' . $query; return false; } } diff --git a/src/Database/Validator/Queries/Document.php b/src/Database/Validator/Queries/Document.php index d6f8792c9..fc33fd2b7 100644 --- a/src/Database/Validator/Queries/Document.php +++ b/src/Database/Validator/Queries/Document.php @@ -6,6 +6,7 @@ use Utopia\Database\Database; use Utopia\Database\Validator\Queries; use Utopia\Database\Validator\Query\Select; +use Utopia\Database\Document as utopiaDocument; class Document extends Queries { @@ -15,21 +16,21 @@ class Document extends Queries */ public function __construct(array $attributes) { - $attributes[] = new \Utopia\Database\Document([ + $attributes[] = new utopiaDocument([ '$id' => '$id', 'key' => '$id', 'type' => Database::VAR_STRING, 'array' => false, ]); - $attributes[] = new \Utopia\Database\Document([ + $attributes[] = new utopiaDocument([ '$id' => '$createdAt', 'key' => '$createdAt', 'type' => Database::VAR_DATETIME, 'array' => false, ]); - $attributes[] = new \Utopia\Database\Document([ + $attributes[] = new utopiaDocument([ '$id' => '$updatedAt', 'key' => '$updatedAt', 'type' => Database::VAR_DATETIME, From 51ff5b031397ef27e789bd14855f13c233282c74 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 15 May 2023 16:12:11 +0300 Subject: [PATCH 43/75] variadic constructor --- src/Database/Validator/Queries.php | 2 +- src/Database/Validator/Queries/Document.php | 2 +- tests/Database/Validator/QueriesTest.php | 16 +++++++++------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index bd1903e66..1a41718f4 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -23,7 +23,7 @@ class Queries extends Validator * * @param array $validators */ - public function __construct(array $validators) + public function __construct(array $validators = []) { $this->validators = $validators; } diff --git a/src/Database/Validator/Queries/Document.php b/src/Database/Validator/Queries/Document.php index fc33fd2b7..e546cde8d 100644 --- a/src/Database/Validator/Queries/Document.php +++ b/src/Database/Validator/Queries/Document.php @@ -41,6 +41,6 @@ public function __construct(array $attributes) new Select($attributes), ]; - parent::__construct(...$validators); + parent::__construct($validators); } } diff --git a/tests/Database/Validator/QueriesTest.php b/tests/Database/Validator/QueriesTest.php index 7ea4b6287..20afc1ee3 100644 --- a/tests/Database/Validator/QueriesTest.php +++ b/tests/Database/Validator/QueriesTest.php @@ -42,13 +42,13 @@ public function testInvalidMethod(): void $validator = new Queries(); $this->assertEquals(false, $validator->isValid(['equal("attr", "value")'])); - $validator = new Queries(new Limit()); + $validator = new Queries([new Limit()]); $this->assertEquals(false, $validator->isValid(['equal("attr", "value")'])); } public function testInvalidValue(): void { - $validator = new Queries(new Limit()); + $validator = new Queries([new Limit()]); $this->assertEquals(false, $validator->isValid(['limit(-1)'])); } @@ -67,11 +67,13 @@ public function testValid(): void ]; $validator = new Queries( - new Cursor(), - new Filter($attributes), - new Limit(), - new Offset(), - new Order($attributes), + [ + new Cursor(), + new Filter($attributes), + new Limit(), + new Offset(), + new Order($attributes) + ] ); $this->assertEquals(true, $validator->isValid(['cursorAfter("asdf")']), $validator->getDescription()); From b24c4811ec0896e2844c5a1b9448be9d3525c133 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 15 May 2023 18:24:24 +0300 Subject: [PATCH 44/75] remove redundant message --- src/Database/Validator/Query/Filter.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index 4095a21d6..5c339073a 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -8,8 +8,6 @@ class Filter extends Base { - protected string $message = 'Invalid query'; - /** * @var array */ From 28c7597c186a1d6c776c6f0d269d18faa300c839 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 16 May 2023 09:52:44 +0300 Subject: [PATCH 45/75] select validator isset fix --- src/Database/Validator/Query/Filter.php | 13 ++++--------- src/Database/Validator/Query/Order.php | 2 +- src/Database/Validator/Query/Select.php | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index 5c339073a..487399973 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -9,14 +9,12 @@ class Filter extends Base { /** - * @var array + * @var array> */ protected array $schema = []; private int $maxValuesCount; - public static bool $NESTING_QUERIES = false; - /** * @param array $attributes * @param int $maxValuesCount @@ -43,7 +41,7 @@ protected function isValidAttribute(string $attribute): bool } // For relationships, just validate the top level. - // Utopia will validate each nested level during the recursive calls. + // will validate each nested level during the recursive calls. $attribute = \explode('.', $attribute)[0]; if (isset($this->schema[$attribute])) { @@ -72,11 +70,8 @@ protected function isValidAttributeAndValues(string $attribute, array $values): return false; } - if (\str_contains($attribute, '.')) { - // Check for special symbol `.` - if (isset($this->schema[$attribute])) { - return true; - } + // isset check if for special symbols "." in the attribute name + if (\str_contains($attribute, '.') && !isset($this->schema[$attribute])) { // For relationships, just validate the top level. // Utopia will validate each nested level during the recursive calls. $attribute = \explode('.', $attribute)[0]; diff --git a/src/Database/Validator/Query/Order.php b/src/Database/Validator/Query/Order.php index c4c275981..bc901b2bf 100644 --- a/src/Database/Validator/Query/Order.php +++ b/src/Database/Validator/Query/Order.php @@ -8,7 +8,7 @@ class Order extends Base { /** - * @var array + * @var array> */ protected array $schema = []; diff --git a/src/Database/Validator/Query/Select.php b/src/Database/Validator/Query/Select.php index 436ea4178..2440d542c 100644 --- a/src/Database/Validator/Query/Select.php +++ b/src/Database/Validator/Query/Select.php @@ -8,7 +8,7 @@ class Select extends Base { /** - * @var array + * @var array> */ protected array $schema = []; From bad239e6681ab073a27cd5e6a4fdb33adf49735f Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 16 May 2023 10:21:10 +0300 Subject: [PATCH 46/75] Check instanceof Query --- src/Database/Validator/Query/Cursor.php | 5 ++++- src/Database/Validator/Query/Filter.php | 16 ++++++++-------- src/Database/Validator/Query/Limit.php | 8 +++++--- src/Database/Validator/Query/Offset.php | 4 ++++ src/Database/Validator/Query/Order.php | 12 ++++++++---- src/Database/Validator/Query/Select.php | 12 +++++++----- 6 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/Database/Validator/Query/Cursor.php b/src/Database/Validator/Query/Cursor.php index 0e19fd7cf..b976d8412 100644 --- a/src/Database/Validator/Query/Cursor.php +++ b/src/Database/Validator/Query/Cursor.php @@ -16,11 +16,14 @@ class Cursor extends Base * Otherwise, returns false * * @param Query $value - * * @return bool */ public function isValid($value): bool { + if (!$value instanceof Query) { + return false; + } + $method = $value->getMethod(); if ($method === Query::TYPE_CURSORAFTER || $method === Query::TYPE_CURSORBEFORE) { diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index 487399973..d655dfed7 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -111,13 +111,13 @@ protected function isValidAttributeAndValues(string $attribute, array $values): * * Otherwise, returns false * - * @param $query + * @param Query $value * @return bool */ - public function isValid($query): bool + public function isValid($value): bool { - $method = $query->getMethod(); - $attribute = $query->getAttribute(); + $method = $value->getMethod(); + $attribute = $value->getAttribute(); switch ($method) { case Query::TYPE_EQUAL: case Query::TYPE_NOTEQUAL: @@ -130,10 +130,10 @@ public function isValid($query): bool case Query::TYPE_ENDS_WITH: case Query::TYPE_BETWEEN: case Query::TYPE_CONTAINS: // todo: What to do about unsupported operators? - $values = $query->getValues(); + $values = $value->getValues(); - if ((is_array($values) && count($values) === 0) || (is_array($values[0]) && count($values[0]) === 0)) { - $this->message = $method . ' queries require at least one value.'; + if (count($values) === 0 || (is_array($values[0]) && count($values[0]) === 0)) { + $this->message = \ucfirst($method) . ' queries require at least one value.'; return false; } @@ -141,7 +141,7 @@ public function isValid($query): bool case Query::TYPE_IS_NULL: case Query::TYPE_IS_NOT_NULL: - return $this->isValidAttributeAndValues($attribute, $query->getValues()); + return $this->isValidAttributeAndValues($attribute, $value->getValues()); default: return false; diff --git a/src/Database/Validator/Query/Limit.php b/src/Database/Validator/Query/Limit.php index c041c6fa1..11d25d351 100644 --- a/src/Database/Validator/Query/Limit.php +++ b/src/Database/Validator/Query/Limit.php @@ -30,10 +30,12 @@ public function __construct(int $maxLimit = PHP_INT_MAX) */ public function isValid($value): bool { - $method = $value->getMethod(); + if (!$value instanceof Query) { + return false; + } - if ($method !== Query::TYPE_LIMIT) { - $this->message = 'Query method invalid: ' . $method; + if ($value->getMethod() !== Query::TYPE_LIMIT) { + $this->message = 'Query method invalid: ' . $value->getMethod(); return false; } diff --git a/src/Database/Validator/Query/Offset.php b/src/Database/Validator/Query/Offset.php index a02c2ffe0..8d59be4d0 100644 --- a/src/Database/Validator/Query/Offset.php +++ b/src/Database/Validator/Query/Offset.php @@ -24,6 +24,10 @@ public function __construct(int $maxOffset = PHP_INT_MAX) */ public function isValid($value): bool { + if (!$value instanceof Query) { + return false; + } + $method = $value->getMethod(); if ($method !== Query::TYPE_OFFSET) { diff --git a/src/Database/Validator/Query/Order.php b/src/Database/Validator/Query/Order.php index bc901b2bf..46e08f7e2 100644 --- a/src/Database/Validator/Query/Order.php +++ b/src/Database/Validator/Query/Order.php @@ -44,13 +44,17 @@ protected function isValidAttribute(string $attribute): bool * * Otherwise, returns false * - * @param Query $query + * @param Query $value * @return bool */ - public function isValid($query): bool + public function isValid($value): bool { - $method = $query->getMethod(); - $attribute = $query->getAttribute(); + if (!$value instanceof Query) { + return false; + } + + $method = $value->getMethod(); + $attribute = $value->getAttribute(); if ($method === Query::TYPE_ORDERASC || $method === Query::TYPE_ORDERDESC) { if ($attribute === '') { diff --git a/src/Database/Validator/Query/Select.php b/src/Database/Validator/Query/Select.php index 2440d542c..b9fa1c7de 100644 --- a/src/Database/Validator/Query/Select.php +++ b/src/Database/Validator/Query/Select.php @@ -29,18 +29,20 @@ public function __construct(array $attributes = []) * * Otherwise, returns false * - * @param $query + * @param Query $value * @return bool */ - public function isValid($query): bool + public function isValid($value): bool { - /* @var $query Query */ + if (!$value instanceof Query) { + return false; + } - if ($query->getMethod() !== Query::TYPE_SELECT) { + if ($value->getMethod() !== Query::TYPE_SELECT) { return false; } - foreach ($query->getValues() as $attribute) { + foreach ($value->getValues() as $attribute) { if (\str_contains($attribute, '.')) { //special symbols with `dots` if (isset($this->schema[$attribute])) { From a0272d901b9ce442bc39164e442bc705d16d76ad Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 16 May 2023 11:13:26 +0300 Subject: [PATCH 47/75] Upper case --- src/Database/Validator/Query/Select.php | 2 +- tests/Database/Base.php | 46 +++++++++---------- .../Validator/DocumentsQueriesTest.php | 2 +- .../Database/Validator/IndexedQueriesTest.php | 1 - 4 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/Database/Validator/Query/Select.php b/src/Database/Validator/Query/Select.php index b9fa1c7de..78b64a5d2 100644 --- a/src/Database/Validator/Query/Select.php +++ b/src/Database/Validator/Query/Select.php @@ -50,7 +50,7 @@ public function isValid($value): bool } // For relationships, just validate the top level. - // Utopia will validate each nested level during the recursive calls. + // Will validate each nested level during the recursive calls. $attribute = \explode('.', $attribute)[0]; } diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 9a40e0760..e503ada36 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -1425,27 +1425,25 @@ public function testFindFloat(): void public function testFindContains(): void { - /** - * Array contains condition - */ - if ($this->getDatabase()->getAdapter()->getSupportForQueryContains()) { - $documents = static::getDatabase()->find('movies', [ - Query::contains('generes', ['comics']) - ]); + if (!$this->getDatabase()->getAdapter()->getSupportForQueryContains()) { + $this->expectNotToPerformAssertions(); + return; + } - $this->assertEquals(2, count($documents)); + $documents = static::getDatabase()->find('movies', [ + Query::contains('generes', ['comics']) + ]); - /** - * Array contains OR condition - */ - $documents = static::getDatabase()->find('movies', [ - Query::contains('generes', ['comics', 'kids']), - ]); + $this->assertEquals(2, count($documents)); - $this->assertEquals(4, count($documents)); - } + /** + * Array contains OR condition + */ + $documents = static::getDatabase()->find('movies', [ + Query::contains('generes', ['comics', 'kids']), + ]); - $this->assertEquals(true, true); // Test must do an assertion + $this->assertEquals(4, count($documents)); } public function testFindFulltext(): void @@ -10934,7 +10932,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: equal queries require at least one value.', $e->getMessage()); + $this->assertEquals('Query not valid: Equal queries require at least one value.', $e->getMessage()); } try { @@ -10954,7 +10952,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: notEqual queries require at least one value.', $e->getMessage()); + $this->assertEquals('Query not valid: NotEqual queries require at least one value.', $e->getMessage()); } try { @@ -10964,7 +10962,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: lessThan queries require at least one value.', $e->getMessage()); + $this->assertEquals('Query not valid: LessThan queries require at least one value.', $e->getMessage()); } try { @@ -10974,7 +10972,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: lessThanEqual queries require at least one value.', $e->getMessage()); + $this->assertEquals('Query not valid: LessThanEqual queries require at least one value.', $e->getMessage()); } try { @@ -10984,7 +10982,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: greaterThan queries require at least one value.', $e->getMessage()); + $this->assertEquals('Query not valid: GreaterThan queries require at least one value.', $e->getMessage()); } try { @@ -10994,7 +10992,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: greaterThanEqual queries require at least one value.', $e->getMessage()); + $this->assertEquals('Query not valid: GreaterThanEqual queries require at least one value.', $e->getMessage()); } try { @@ -11004,7 +11002,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: contains queries require at least one value.', $e->getMessage()); + $this->assertEquals('Query not valid: Contains queries require at least one value.', $e->getMessage()); } } diff --git a/tests/Database/Validator/DocumentsQueriesTest.php b/tests/Database/Validator/DocumentsQueriesTest.php index 94c4d773b..df0191fd9 100644 --- a/tests/Database/Validator/DocumentsQueriesTest.php +++ b/tests/Database/Validator/DocumentsQueriesTest.php @@ -174,6 +174,6 @@ public function testInvalidQueries(): void $queries = ['equal("title", [])']; // empty array $this->assertEquals(false, $validator->isValid($queries)); - $this->assertEquals('Query not valid: equal queries require at least one value.', $validator->getDescription()); + $this->assertEquals('Query not valid: Equal queries require at least one value.', $validator->getDescription()); } } diff --git a/tests/Database/Validator/IndexedQueriesTest.php b/tests/Database/Validator/IndexedQueriesTest.php index c55aabd5e..288ae2157 100644 --- a/tests/Database/Validator/IndexedQueriesTest.php +++ b/tests/Database/Validator/IndexedQueriesTest.php @@ -2,7 +2,6 @@ namespace Utopia\Tests\Validator; -use Exception; use PHPUnit\Framework\TestCase; use Utopia\Database\Database; use Utopia\Database\Document; From 448f6a4ab68f1e14a1a5a776a19a97c24a5b1b12 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 16 May 2023 11:45:02 +0300 Subject: [PATCH 48/75] Uncomment ambiguous syntax --- tests/Database/Base.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index e503ada36..ea9b63c10 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -273,17 +273,17 @@ public function testAttributeKeyWithSymbols(): void public function testSymbolDots(): void { - static::getDatabase()->createCollection('dots.father'); + static::getDatabase()->createCollection('dots.parent'); $this->assertTrue(static::getDatabase()->createAttribute( - collection: 'dots.father', + collection: 'dots.parent', id: 'dots.name', type: Database::VAR_STRING, size: 255, required: false )); - $document = static::getDatabase()->find('dots.father', [ + $document = static::getDatabase()->find('dots.parent', [ Query::select(['dots.name']), ]); $this->assertEmpty($document); @@ -299,14 +299,14 @@ public function testSymbolDots(): void )); static::getDatabase()->createRelationship( - collection: 'dots.father', + collection: 'dots.parent', relatedCollection: 'dots', type: Database::RELATION_ONE_TO_ONE ); - static::getDatabase()->createDocument('dots.father', new Document([ + static::getDatabase()->createDocument('dots.parent', new Document([ '$id' => ID::custom('father'), - 'dots.name' => 'shmuel', + 'dots.name' => 'Bill clinton', '$permissions' => [ Permission::read(Role::any()), Permission::create(Role::any()), @@ -324,18 +324,18 @@ public function testSymbolDots(): void ] ])); - $documents = static::getDatabase()->find('dots.father', [ + $documents = static::getDatabase()->find('dots.parent', [ Query::select(['*']), ]); - $this->assertEquals('shmuel', $documents[0]['dots.name']); + $this->assertEquals('Bill clinton', $documents[0]['dots.name']); //todo: ambiguous syntax for parent attribute VS relation attribute -// $documents = static::getDatabase()->find('dots.father', [ -// Query::select(['dots.name']) -// ]); -// -// $this->assertEquals('shmuel', $documents[0]['dots.name']); + $documents = static::getDatabase()->find('dots.parent', [ + Query::select(['dots.name']) + ]); + + $this->assertEquals(null, $documents[0]['dots.name']); // Todo: Should be 'Bill clinton' } /** From bee4314023d569acfeef2ac3f01e45f43ce338ee Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 16 May 2023 12:18:34 +0300 Subject: [PATCH 49/75] testAttributeNamesWithDots --- tests/Database/Base.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tests/Database/Base.php b/tests/Database/Base.php index ea9b63c10..d011d0c39 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -271,7 +271,7 @@ public function testAttributeKeyWithSymbols(): void $this->assertEquals('value', $document->getAttribute('key_with.sym$bols')); } - public function testSymbolDots(): void + public function testAttributeNamesWithDots(): void { static::getDatabase()->createCollection('dots.parent'); @@ -5447,14 +5447,6 @@ public function testOneToManyTwoWayRelationship(): void $customer1->setAttribute('accounts', ['account1', 'account2']) ); -// // Query related document again -// $customers = static::getDatabase()->find('customer', [ -// Query::equal('accounts.name', ['Account 2 Updated']) -// ]); -// -// $this->assertEquals(1, \count($customers)); -// $this->assertEquals(2, \count($customers[0]['accounts'])); - // Update inverse document static::getDatabase()->updateDocument( 'account', From fb7c7f20e9761b8cf504212b32ecb07e425c59a4 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 16 May 2023 13:00:38 +0300 Subject: [PATCH 50/75] remove status --- tests/Database/Validator/IndexedQueriesTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Database/Validator/IndexedQueriesTest.php b/tests/Database/Validator/IndexedQueriesTest.php index 288ae2157..202672b0c 100644 --- a/tests/Database/Validator/IndexedQueriesTest.php +++ b/tests/Database/Validator/IndexedQueriesTest.php @@ -101,13 +101,14 @@ public function testMissingIndex(): void 'array' => false, ]), ]; + $indexes = [ new Document([ - 'status' => 'available', 'type' => Database::INDEX_KEY, 'attributes' => ['name'], ]), ]; + $validator = new IndexedQueries( $attributes, $indexes, From aa72d655183b7a6d902d1b3c668d5decbc4ecdd9 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 16 May 2023 18:28:17 +0300 Subject: [PATCH 51/75] Remove comment + Cursor investigation + lint --- phpunit.xml | 2 +- src/Database/Validator/Queries/Documents.php | 2 +- tests/Database/Adapter/MongoDBTest.php | 203 +++++++++--------- tests/Database/Adapter/MySQLTest.php | 151 ++++++------- .../Validator/DocumentsQueriesTest.php | 8 +- .../Database/Validator/IndexedQueriesTest.php | 13 +- tests/Database/Validator/QueryTest.php | 22 +- 7 files changed, 206 insertions(+), 195 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index 3833748e0..31b947dd6 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true"> + stopOnFailure="false"> ./tests/ diff --git a/src/Database/Validator/Queries/Documents.php b/src/Database/Validator/Queries/Documents.php index 742015eb0..2dd7c0819 100644 --- a/src/Database/Validator/Queries/Documents.php +++ b/src/Database/Validator/Queries/Documents.php @@ -48,7 +48,7 @@ public function __construct(array $attributes, array $indexes) $validators = [ new Limit(), new Offset(), - new Cursor(), // I think this should be checks against $attributes? + new Cursor(), new Filter($attributes), new Order($attributes), new Select($attributes), diff --git a/tests/Database/Adapter/MongoDBTest.php b/tests/Database/Adapter/MongoDBTest.php index 563837006..6cbb74419 100644 --- a/tests/Database/Adapter/MongoDBTest.php +++ b/tests/Database/Adapter/MongoDBTest.php @@ -1,103 +1,104 @@ connect('redis', 6379); - $redis->flushAll(); - $cache = new Cache(new RedisAdapter($redis)); - - $schema = 'utopiaTests'; // same as $this->testDatabase - $client = new Client( - $schema, - 'mongo', - 27017, - 'root', - 'example', - false - ); - - $database = new Database(new Mongo($client), $cache); - $database->setDefaultDatabase($schema); - $database->setNamespace('myapp_' . uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } - - /** - * @throws Exception - */ - public function testCreateExistsDelete(): void - { - // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. - $this->assertNotNull(static::getDatabase()->create()); - $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); - $this->assertEquals(true, static::getDatabase()->create()); - $this->assertEquals(true, static::getDatabase()->setDefaultDatabase($this->testDatabase)); - } - - public function testRenameAttribute(): void - { - $this->assertTrue(true); - } - - public function testRenameAttributeExisting(): void - { - $this->assertTrue(true); - } - - public function testUpdateAttributeStructure(): void - { - $this->assertTrue(true); - } - - public function testKeywords(): void - { - $this->assertTrue(true); - } - - public static function killDatabase(): void - { - self::$database = null; - } -} +// +//namespace Utopia\Tests\Adapter; +// +//use Exception; +//use Redis; +//use Utopia\Cache\Cache; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Database\Adapter\Mongo; +//use Utopia\Database\Database; +//use Utopia\Mongo\Client; +//use Utopia\Tests\Base; +// +//class MongoDBTest extends Base +//{ +// public static ?Database $database = null; +// +// +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "mongodb"; +// } +// +// /** +// * @return Database +// * @throws Exception +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// $cache = new Cache(new RedisAdapter($redis)); +// +// $schema = 'utopiaTests'; // same as $this->testDatabase +// $client = new Client( +// $schema, +// 'mongo', +// 27017, +// 'root', +// 'example', +// false +// ); +// +// $database = new Database(new Mongo($client), $cache); +// $database->setDefaultDatabase($schema); +// $database->setNamespace('myapp_' . uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +// +// /** +// * @throws Exception +// */ +// public function testCreateExistsDelete(): void +// { +// // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. +// $this->assertNotNull(static::getDatabase()->create()); +// $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); +// $this->assertEquals(true, static::getDatabase()->create()); +// $this->assertEquals(true, static::getDatabase()->setDefaultDatabase($this->testDatabase)); +// } +// +// public function testRenameAttribute(): void +// { +// $this->assertTrue(true); +// } +// +// public function testRenameAttributeExisting(): void +// { +// $this->assertTrue(true); +// } +// +// public function testUpdateAttributeStructure(): void +// { +// $this->assertTrue(true); +// } +// +// public function testKeywords(): void +// { +// $this->assertTrue(true); +// } +// +// public static function killDatabase(): void +// { +// self::$database = null; +// } +//} diff --git a/tests/Database/Adapter/MySQLTest.php b/tests/Database/Adapter/MySQLTest.php index f0ee5822b..accd89fa5 100644 --- a/tests/Database/Adapter/MySQLTest.php +++ b/tests/Database/Adapter/MySQLTest.php @@ -1,77 +1,78 @@ connect('redis', 6379); - $redis->flushAll(); - - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new MySQL($pdo), $cache); - $database->setDefaultDatabase('utopiaTests'); - $database->setNamespace('myapp_'.uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } - - public static function killDatabase(): void - { - self::$database = null; - } -} +// +//namespace Utopia\Tests\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Cache\Cache; +//use Utopia\Database\Database; +//use Utopia\Database\Adapter\MySQL; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Tests\Base; +// +//class MySQLTest extends Base +//{ +// public static ?Database $database = null; +// +// // TODO@kodumbeats hacky way to identify adapters for tests +// // Remove once all methods are implemented +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "mysql"; +// } +// +// /** +// * +// * @return int +// */ +// public static function getUsedIndexes(): int +// { +// return MySQL::getCountOfDefaultIndexes(); +// } +// +// /** +// * @return Database +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $dbHost = 'mysql'; +// $dbPort = '3307'; +// $dbUser = 'root'; +// $dbPass = 'password'; +// +// $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MySQL::getPDOAttributes()); +// +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new MySQL($pdo), $cache); +// $database->setDefaultDatabase('utopiaTests'); +// $database->setNamespace('myapp_'.uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +// +// public static function killDatabase(): void +// { +// self::$database = null; +// } +//} diff --git a/tests/Database/Validator/DocumentsQueriesTest.php b/tests/Database/Validator/DocumentsQueriesTest.php index df0191fd9..289a80bdb 100644 --- a/tests/Database/Validator/DocumentsQueriesTest.php +++ b/tests/Database/Validator/DocumentsQueriesTest.php @@ -8,7 +8,7 @@ use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; -use Utopia\Database\Validator\Queries\Documents as DocumentsQueries; +use Utopia\Database\Validator\Queries\Documents; class DocumentsQueriesTest extends TestCase { @@ -113,7 +113,7 @@ public function tearDown(): void */ public function testValidQueries(): void { - $validator = new DocumentsQueries($this->collection['attributes'], $this->collection['indexes']); + $validator = new Documents($this->collection['attributes'], $this->collection['indexes']); $queries = [ 'notEqual("title", ["Iron Man", "Ant Man"])', @@ -131,7 +131,7 @@ public function testValidQueries(): void 'isNull("title")', 'isNotNull("title")', 'cursorAfter("a")', - 'cursorBefore("b")', // Todo: This should fail? + 'cursorBefore("b")', 'orderAsc("title")', 'limit(10)', 'offset(10)', @@ -149,7 +149,7 @@ public function testValidQueries(): void */ public function testInvalidQueries(): void { - $validator = new DocumentsQueries($this->collection['attributes'], $this->collection['indexes']); + $validator = new Documents($this->collection['attributes'], $this->collection['indexes']); $queries = ['search("description", "iron")']; $this->assertEquals(false, $validator->isValid($queries)); diff --git a/tests/Database/Validator/IndexedQueriesTest.php b/tests/Database/Validator/IndexedQueriesTest.php index 202672b0c..2a4396e5e 100644 --- a/tests/Database/Validator/IndexedQueriesTest.php +++ b/tests/Database/Validator/IndexedQueriesTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\TestCase; use Utopia\Database\Database; use Utopia\Database\Document; +use Utopia\Database\Query; use Utopia\Database\Validator\IndexedQueries; use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\Query\Filter; @@ -61,18 +62,18 @@ public function testValid(): void '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, @@ -84,12 +85,19 @@ public function testValid(): void new Order($attributes) ] ); + $this->assertEquals(true, $validator->isValid(['cursorAfter("asdf")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::cursorAfter(new Document(['$id'=>'asdf']))]), $validator->getDescription()); $this->assertEquals(true, $validator->isValid(['equal("name", "value")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::equal('name', ['value'])]), $validator->getDescription()); $this->assertEquals(true, $validator->isValid(['limit(10)']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::limit(10)]), $validator->getDescription()); $this->assertEquals(true, $validator->isValid(['offset(10)']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::offset(10)]), $validator->getDescription()); $this->assertEquals(true, $validator->isValid(['orderAsc("name")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::orderAsc('name')]), $validator->getDescription()); $this->assertEquals(true, $validator->isValid(['search("name", "value")']), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::search('name', 'value')]), $validator->getDescription()); } public function testMissingIndex(): void @@ -120,6 +128,7 @@ public function testMissingIndex(): void 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/Database/Validator/QueryTest.php b/tests/Database/Validator/QueryTest.php index d5bb0b8c0..e77e663b0 100644 --- a/tests/Database/Validator/QueryTest.php +++ b/tests/Database/Validator/QueryTest.php @@ -7,7 +7,7 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; -use Utopia\Database\Validator\Queries\Documents as DocumentsQueries; +use Utopia\Database\Validator\Queries\Documents; class QueryTest extends TestCase { @@ -108,7 +108,7 @@ public function tearDown(): void */ public function testQuery(): void { - $validator = new DocumentsQueries($this->attributes, []); + $validator = new Documents($this->attributes, []); $this->assertEquals(true, $validator->isValid([Query::parse('equal("$id", ["Iron Man", "Ant Man"])')])); $this->assertEquals(true, $validator->isValid([Query::parse('equal("$id", "Iron Man")')])); @@ -137,7 +137,7 @@ public function testQuery(): void */ public function testInvalidMethod(): void { - $validator = new DocumentsQueries($this->attributes, []); + $validator = new Documents($this->attributes, []); $this->assertEquals(false, $validator->isValid([Query::parse('eqqual("title", "Iron Man")')])); $this->assertEquals('Query method not valid: eqqual', $validator->getDescription()); @@ -148,7 +148,7 @@ public function testInvalidMethod(): void */ public function testAttributeNotFound(): void { - $validator = new DocumentsQueries($this->attributes, []); + $validator = new Documents($this->attributes, []); $response = $validator->isValid([Query::parse('equal("name", "Iron Man")')]); @@ -166,7 +166,7 @@ public function testAttributeNotFound(): void */ public function testAttributeWrongType(): void { - $validator = new DocumentsQueries($this->attributes, []); + $validator = new Documents($this->attributes, []); $response = $validator->isValid([Query::parse('equal("title", 1776)')]); @@ -179,7 +179,7 @@ public function testAttributeWrongType(): void */ public function testQueryDate(): void { - $validator = new DocumentsQueries($this->attributes, []); + $validator = new Documents($this->attributes, []); $response = $validator->isValid([Query::parse('greaterThan("birthDay", "1960-01-01 10:10:10")')]); $this->assertEquals(true, $response); } @@ -189,7 +189,7 @@ public function testQueryDate(): void */ public function testQueryLimit(): void { - $validator = new DocumentsQueries($this->attributes, []); + $validator = new Documents($this->attributes, []); $response = $validator->isValid([Query::parse('limit(25)')]); $this->assertEquals(true, $response); @@ -209,7 +209,7 @@ public function testQueryLimit(): void */ public function testQueryOffset(): void { - $validator = new DocumentsQueries($this->attributes, []); + $validator = new Documents($this->attributes, []); $response = $validator->isValid([Query::parse('offset(25)')]); $this->assertEquals(true, $response); @@ -229,7 +229,7 @@ public function testQueryOffset(): void */ public function testQueryOrder(): void { - $validator = new DocumentsQueries($this->attributes, []); + $validator = new Documents($this->attributes, []); $response = $validator->isValid([Query::parse('orderAsc("title")')]); $this->assertEquals(true, $response); @@ -249,7 +249,7 @@ public function testQueryOrder(): void */ public function testQueryCursor(): void { - $validator = new DocumentsQueries($this->attributes, []); + $validator = new Documents($this->attributes, []); $response = $validator->isValid([Query::parse('cursorAfter("asdf")')]); $this->assertEquals(true, $response); @@ -282,7 +282,7 @@ public function testQueryGetByType(): void */ public function testQueryEmpty(): void { - $validator = new DocumentsQueries($this->attributes, []); + $validator = new Documents($this->attributes, []); $response = $validator->isValid([Query::equal('title', [''])]); $this->assertEquals(true, $response); From 0c7aa6219b6ad60d62f7f2a707e97dcf3244952f Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 16 May 2023 19:48:15 +0300 Subject: [PATCH 52/75] Remove comment + Cursor investigation + lint --- src/Database/Validator/Queries.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index 1a41718f4..267f62bd2 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -41,7 +41,7 @@ public function getDescription(): string } /** - * @param mixed $value + * @param array $value * @return bool */ public function isValid($value): bool From 3af9a10c00e1cc8bd9d56b7fa0503292d9a956af Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 16 May 2023 19:49:33 +0300 Subject: [PATCH 53/75] Explicit Hint for validator --- src/Database/Validator/Queries.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index 267f62bd2..5efdac115 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -41,7 +41,7 @@ public function getDescription(): string } /** - * @param array $value + * @param array $value * @return bool */ public function isValid($value): bool From 46fb1595ae45e45f3293b73170fdbeb0b0da8b2e Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 21 May 2023 10:19:23 +0300 Subject: [PATCH 54/75] alias DocumentValidator + Invalid query --- composer.lock | 58 ++++++++++----------- src/Database/Validator/Queries.php | 4 +- src/Database/Validator/Queries/Document.php | 8 +-- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/composer.lock b/composer.lock index 7b1ac6551..bea5328f7 100644 --- a/composer.lock +++ b/composer.lock @@ -512,16 +512,16 @@ }, { "name": "fakerphp/faker", - "version": "v1.21.0", + "version": "v1.22.0", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "92efad6a967f0b79c499705c69b662f738cc9e4d" + "reference": "f85772abd508bd04e20bb4b1bbe260a68d0066d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/92efad6a967f0b79c499705c69b662f738cc9e4d", - "reference": "92efad6a967f0b79c499705c69b662f738cc9e4d", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/f85772abd508bd04e20bb4b1bbe260a68d0066d2", + "reference": "f85772abd508bd04e20bb4b1bbe260a68d0066d2", "shasum": "" }, "require": { @@ -574,9 +574,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.21.0" + "source": "https://github.com/FakerPHP/Faker/tree/v1.22.0" }, - "time": "2022-12-13T13:54:32+00:00" + "time": "2023-05-14T12:31:37+00:00" }, { "name": "laravel/pint", @@ -705,16 +705,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.15.4", + "version": "v4.15.5", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" + "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e", + "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e", "shasum": "" }, "require": { @@ -755,9 +755,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5" }, - "time": "2023-03-05T19:49:14+00:00" + "time": "2023-05-19T20:20:00+00:00" }, { "name": "pcov/clobber", @@ -906,16 +906,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.14", + "version": "1.10.15", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d232901b09e67538e5c86a724be841bea5768a7c" + "reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d232901b09e67538e5c86a724be841bea5768a7c", - "reference": "d232901b09e67538e5c86a724be841bea5768a7c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/762c4dac4da6f8756eebb80e528c3a47855da9bd", + "reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd", "shasum": "" }, "require": { @@ -964,7 +964,7 @@ "type": "tidelift" } ], - "time": "2023-04-19T13:47:27+00:00" + "time": "2023-05-09T15:28:01+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1286,16 +1286,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.7", + "version": "9.6.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2" + "reference": "17d621b3aff84d0c8b62539e269e87d8d5baa76e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", - "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/17d621b3aff84d0c8b62539e269e87d8d5baa76e", + "reference": "17d621b3aff84d0c8b62539e269e87d8d5baa76e", "shasum": "" }, "require": { @@ -1369,7 +1369,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.8" }, "funding": [ { @@ -1385,7 +1385,7 @@ "type": "tidelift" } ], - "time": "2023-04-14T08:58:40+00:00" + "time": "2023-05-11T05:14:45+00:00" }, { "name": "psr/container", @@ -1786,16 +1786,16 @@ }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", "shasum": "" }, "require": { @@ -1840,7 +1840,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" }, "funding": [ { @@ -1848,7 +1848,7 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2023-05-07T05:35:17+00:00" }, { "name": "sebastian/environment", diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index 5efdac115..d3d6d2317 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -55,8 +55,8 @@ public function isValid($value): bool if (!$query instanceof Query) { try { $query = Query::parse($query); - } catch (\Throwable) { - $this->message = 'Invalid query: ' . $query; + } catch (\Throwable $e) { + $this->message = $e->getMessage(); return false; } } diff --git a/src/Database/Validator/Queries/Document.php b/src/Database/Validator/Queries/Document.php index e546cde8d..f62fb8bbf 100644 --- a/src/Database/Validator/Queries/Document.php +++ b/src/Database/Validator/Queries/Document.php @@ -6,7 +6,7 @@ use Utopia\Database\Database; use Utopia\Database\Validator\Queries; use Utopia\Database\Validator\Query\Select; -use Utopia\Database\Document as utopiaDocument; +use Utopia\Database\Document as DocumentValidator; class Document extends Queries { @@ -16,21 +16,21 @@ class Document extends Queries */ public function __construct(array $attributes) { - $attributes[] = new utopiaDocument([ + $attributes[] = new DocumentValidator([ '$id' => '$id', 'key' => '$id', 'type' => Database::VAR_STRING, 'array' => false, ]); - $attributes[] = new utopiaDocument([ + $attributes[] = new DocumentValidator([ '$id' => '$createdAt', 'key' => '$createdAt', 'type' => Database::VAR_DATETIME, 'array' => false, ]); - $attributes[] = new utopiaDocument([ + $attributes[] = new DocumentValidator([ '$id' => '$updatedAt', 'key' => '$updatedAt', 'type' => Database::VAR_DATETIME, From c08a623c9abe2f887c83199046e798575e859f1f Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 21 May 2023 10:45:12 +0300 Subject: [PATCH 55/75] alias DocumentValidator + Invalid query + remove ambiguous syntax --- src/Database/Validator/Query/Filter.php | 2 +- tests/Database/Base.php | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index d655dfed7..f817b2320 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -129,7 +129,7 @@ public function isValid($value): bool case Query::TYPE_STARTS_WITH: case Query::TYPE_ENDS_WITH: case Query::TYPE_BETWEEN: - case Query::TYPE_CONTAINS: // todo: What to do about unsupported operators? + case Query::TYPE_CONTAINS: $values = $value->getValues(); if (count($values) === 0 || (is_array($values[0]) && count($values[0]) === 0)) { diff --git a/tests/Database/Base.php b/tests/Database/Base.php index d011d0c39..8f64b2457 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -329,13 +329,6 @@ public function testAttributeNamesWithDots(): void ]); $this->assertEquals('Bill clinton', $documents[0]['dots.name']); - - //todo: ambiguous syntax for parent attribute VS relation attribute - $documents = static::getDatabase()->find('dots.parent', [ - Query::select(['dots.name']) - ]); - - $this->assertEquals(null, $documents[0]['dots.name']); // Todo: Should be 'Bill clinton' } /** From 0321314587b674a72a36c773e7de4397996e9eda Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 21 May 2023 12:51:32 +0300 Subject: [PATCH 56/75] Empty queries values --- phpunit.xml | 2 +- src/Database/Validator/Query/Filter.php | 7 ++++++- tests/Database/Base.php | 10 +++++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index 31b947dd6..3833748e0 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false"> + stopOnFailure="true"> ./tests/ diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index f817b2320..c37b69aba 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -132,11 +132,16 @@ public function isValid($value): bool case Query::TYPE_CONTAINS: $values = $value->getValues(); - if (count($values) === 0 || (is_array($values[0]) && count($values[0]) === 0)) { + if (count($values) === 0) { $this->message = \ucfirst($method) . ' queries require at least one value.'; return false; } + if (is_array($values[0]) && count($values[0]) === 0) { + $this->message = \ucfirst($method) . ' can take only one value.'; + return false; + } + return $this->isValidAttributeAndValues($attribute, $values); case Query::TYPE_IS_NULL: diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 8f64b2457..38a74b434 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -10937,7 +10937,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: NotEqual queries require at least one value.', $e->getMessage()); + $this->assertEquals('Query not valid: NotEqual can take only one value.', $e->getMessage()); } try { @@ -10947,7 +10947,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: LessThan queries require at least one value.', $e->getMessage()); + $this->assertEquals('Query not valid: LessThan can take only one value.', $e->getMessage()); } try { @@ -10957,7 +10957,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: LessThanEqual queries require at least one value.', $e->getMessage()); + $this->assertEquals('Query not valid: LessThanEqual can take only one value.', $e->getMessage()); } try { @@ -10967,7 +10967,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: GreaterThan queries require at least one value.', $e->getMessage()); + $this->assertEquals('Query not valid: GreaterThan can take only one value.', $e->getMessage()); } try { @@ -10977,7 +10977,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: GreaterThanEqual queries require at least one value.', $e->getMessage()); + $this->assertEquals('Query not valid: GreaterThanEqual can take only one value.', $e->getMessage()); } try { From 875851cadff8085c038f592e7376d4486e4c7b52 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 21 May 2023 13:32:30 +0300 Subject: [PATCH 57/75] Split tests for success and failure --- tests/Database/Adapter/MongoDBTest.php | 203 +++++++++--------- tests/Database/Adapter/MySQLTest.php | 151 +++++++------ tests/Database/Validator/Query/CursorTest.php | 28 +-- tests/Database/Validator/Query/FilterTest.php | 65 +++--- tests/Database/Validator/Query/LimitTest.php | 21 +- tests/Database/Validator/Query/OffsetTest.php | 28 +-- tests/Database/Validator/Query/OrderTest.php | 42 ++-- tests/Database/Validator/Query/SelectTest.php | 20 +- 8 files changed, 295 insertions(+), 263 deletions(-) diff --git a/tests/Database/Adapter/MongoDBTest.php b/tests/Database/Adapter/MongoDBTest.php index 6cbb74419..563837006 100644 --- a/tests/Database/Adapter/MongoDBTest.php +++ b/tests/Database/Adapter/MongoDBTest.php @@ -1,104 +1,103 @@ connect('redis', 6379); -// $redis->flushAll(); -// $cache = new Cache(new RedisAdapter($redis)); -// -// $schema = 'utopiaTests'; // same as $this->testDatabase -// $client = new Client( -// $schema, -// 'mongo', -// 27017, -// 'root', -// 'example', -// false -// ); -// -// $database = new Database(new Mongo($client), $cache); -// $database->setDefaultDatabase($schema); -// $database->setNamespace('myapp_' . uniqid()); -// -// if ($database->exists('utopiaTests')) { -// $database->delete('utopiaTests'); -// } -// -// $database->create(); -// -// return self::$database = $database; -// } -// -// /** -// * @throws Exception -// */ -// public function testCreateExistsDelete(): void -// { -// // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. -// $this->assertNotNull(static::getDatabase()->create()); -// $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); -// $this->assertEquals(true, static::getDatabase()->create()); -// $this->assertEquals(true, static::getDatabase()->setDefaultDatabase($this->testDatabase)); -// } -// -// public function testRenameAttribute(): void -// { -// $this->assertTrue(true); -// } -// -// public function testRenameAttributeExisting(): void -// { -// $this->assertTrue(true); -// } -// -// public function testUpdateAttributeStructure(): void -// { -// $this->assertTrue(true); -// } -// -// public function testKeywords(): void -// { -// $this->assertTrue(true); -// } -// -// public static function killDatabase(): void -// { -// self::$database = null; -// } -//} +namespace Utopia\Tests\Adapter; + +use Exception; +use Redis; +use Utopia\Cache\Cache; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Database\Adapter\Mongo; +use Utopia\Database\Database; +use Utopia\Mongo\Client; +use Utopia\Tests\Base; + +class MongoDBTest extends Base +{ + public static ?Database $database = null; + + + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "mongodb"; + } + + /** + * @return Database + * @throws Exception + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + $cache = new Cache(new RedisAdapter($redis)); + + $schema = 'utopiaTests'; // same as $this->testDatabase + $client = new Client( + $schema, + 'mongo', + 27017, + 'root', + 'example', + false + ); + + $database = new Database(new Mongo($client), $cache); + $database->setDefaultDatabase($schema); + $database->setNamespace('myapp_' . uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + + $database->create(); + + return self::$database = $database; + } + + /** + * @throws Exception + */ + public function testCreateExistsDelete(): void + { + // Mongo creates databases on the fly, so exists would always pass. So we override this test to remove the exists check. + $this->assertNotNull(static::getDatabase()->create()); + $this->assertEquals(true, static::getDatabase()->delete($this->testDatabase)); + $this->assertEquals(true, static::getDatabase()->create()); + $this->assertEquals(true, static::getDatabase()->setDefaultDatabase($this->testDatabase)); + } + + public function testRenameAttribute(): void + { + $this->assertTrue(true); + } + + public function testRenameAttributeExisting(): void + { + $this->assertTrue(true); + } + + public function testUpdateAttributeStructure(): void + { + $this->assertTrue(true); + } + + public function testKeywords(): void + { + $this->assertTrue(true); + } + + public static function killDatabase(): void + { + self::$database = null; + } +} diff --git a/tests/Database/Adapter/MySQLTest.php b/tests/Database/Adapter/MySQLTest.php index accd89fa5..f0ee5822b 100644 --- a/tests/Database/Adapter/MySQLTest.php +++ b/tests/Database/Adapter/MySQLTest.php @@ -1,78 +1,77 @@ connect('redis', 6379); -// $redis->flushAll(); -// -// $cache = new Cache(new RedisAdapter($redis)); -// -// $database = new Database(new MySQL($pdo), $cache); -// $database->setDefaultDatabase('utopiaTests'); -// $database->setNamespace('myapp_'.uniqid()); -// -// if ($database->exists('utopiaTests')) { -// $database->delete('utopiaTests'); -// } -// -// $database->create(); -// -// return self::$database = $database; -// } -// -// public static function killDatabase(): void -// { -// self::$database = null; -// } -//} +namespace Utopia\Tests\Adapter; + +use PDO; +use Redis; +use Utopia\Cache\Cache; +use Utopia\Database\Database; +use Utopia\Database\Adapter\MySQL; +use Utopia\Cache\Adapter\Redis as RedisAdapter; +use Utopia\Tests\Base; + +class MySQLTest extends Base +{ + public static ?Database $database = null; + + // TODO@kodumbeats hacky way to identify adapters for tests + // Remove once all methods are implemented + /** + * Return name of adapter + * + * @return string + */ + public static function getAdapterName(): string + { + return "mysql"; + } + + /** + * + * @return int + */ + public static function getUsedIndexes(): int + { + return MySQL::getCountOfDefaultIndexes(); + } + + /** + * @return Database + */ + public static function getDatabase(): Database + { + if (!is_null(self::$database)) { + return self::$database; + } + + $dbHost = 'mysql'; + $dbPort = '3307'; + $dbUser = 'root'; + $dbPass = 'password'; + + $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MySQL::getPDOAttributes()); + + $redis = new Redis(); + $redis->connect('redis', 6379); + $redis->flushAll(); + + $cache = new Cache(new RedisAdapter($redis)); + + $database = new Database(new MySQL($pdo), $cache); + $database->setDefaultDatabase('utopiaTests'); + $database->setNamespace('myapp_'.uniqid()); + + if ($database->exists('utopiaTests')) { + $database->delete('utopiaTests'); + } + + $database->create(); + + return self::$database = $database; + } + + public static function killDatabase(): void + { + self::$database = null; + } +} diff --git a/tests/Database/Validator/Query/CursorTest.php b/tests/Database/Validator/Query/CursorTest.php index a4114351b..f68d2bd3a 100644 --- a/tests/Database/Validator/Query/CursorTest.php +++ b/tests/Database/Validator/Query/CursorTest.php @@ -8,21 +8,25 @@ class CursorTest extends TestCase { - public function testValue(): void + public function testValueSuccess(): void { $validator = new Cursor(); - // Test for Success - $this->assertEquals($validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), true, $validator->getDescription()); + $this->assertTrue($validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf']))); + $this->assertTrue($validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf']))); + } + + public function testValueFailure(): void + { + $validator = new Cursor(); - // Test for Failure - $this->assertEquals($validator->isValid(Query::limit(-1)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::limit(101)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::offset(-1)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::offset(5001)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::equal('attr', ['v'])), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::orderAsc('attr')), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::orderDesc('attr')), false, $validator->getDescription()); + $this->assertFalse($validator->isValid(Query::limit(-1))); + $this->assertEquals('Invalid query', $validator->getDescription()); + $this->assertFalse($validator->isValid(Query::limit(101))); + $this->assertFalse($validator->isValid(Query::offset(-1))); + $this->assertFalse($validator->isValid(Query::offset(5001))); + $this->assertFalse($validator->isValid(Query::equal('attr', ['v']))); + $this->assertFalse($validator->isValid(Query::orderAsc('attr'))); + $this->assertFalse($validator->isValid(Query::orderDesc('attr'))); } } diff --git a/tests/Database/Validator/Query/FilterTest.php b/tests/Database/Validator/Query/FilterTest.php index 714b24ff1..0537c8888 100644 --- a/tests/Database/Validator/Query/FilterTest.php +++ b/tests/Database/Validator/Query/FilterTest.php @@ -2,21 +2,20 @@ namespace Utopia\Tests\Validator\Query; -use Exception; use PHPUnit\Framework\TestCase; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; +use Utopia\Database\Validator\Query\Base; use Utopia\Database\Validator\Query\Filter; class FilterTest extends TestCase { - /** - * @throws Exception - */ - public function testValue(): void + protected Base|null $validator = null; + + public function setUp(): void { - $validator = new Filter( + $this->validator = new Filter( attributes: [ new Document([ '$id' => 'attr', @@ -26,30 +25,36 @@ public function testValue(): void ]), ], ); - // Test for Success - $this->assertEquals($validator->isValid(Query::between('attr', '1975-12-06', '2050-12-06')), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::isNotNull('attr')), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::isNull('attr')), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::startsWith('attr', 'super')), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::endsWith('attr', 'man')), true, $validator->getDescription()); + } + + public function testSuccess(): void + { + $this->assertTrue($this->validator->isValid(Query::between('attr', '1975-12-06', '2050-12-06'))); + $this->assertTrue($this->validator->isValid(Query::isNotNull('attr'))); + $this->assertTrue($this->validator->isValid(Query::isNull('attr'))); + $this->assertTrue($this->validator->isValid(Query::startsWith('attr', 'super'))); + $this->assertTrue($this->validator->isValid(Query::endsWith('attr', 'man'))); + } - // Test for Failure - $this->assertEquals($validator->isValid(Query::select(['attr'])), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::limit(1)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::limit(0)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::limit(100)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::limit(-1)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::limit(101)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::offset(1)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::offset(0)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::offset(5000)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::offset(-1)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::offset(5001)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::equal('dne', ['v'])), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::equal('', ['v'])), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::orderAsc('attr')), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::orderDesc('attr')), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), false, $validator->getDescription()); + public function testFailure(): void + { + $this->assertFalse($this->validator->isValid(Query::select(['attr']))); + $this->assertEquals('Invalid query', $this->validator->getDescription()); + $this->assertFalse($this->validator->isValid(Query::limit(1))); + $this->assertFalse($this->validator->isValid(Query::limit(0))); + $this->assertFalse($this->validator->isValid(Query::limit(100))); + $this->assertFalse($this->validator->isValid(Query::limit(-1))); + $this->assertFalse($this->validator->isValid(Query::limit(101))); + $this->assertFalse($this->validator->isValid(Query::offset(1))); + $this->assertFalse($this->validator->isValid(Query::offset(0))); + $this->assertFalse($this->validator->isValid(Query::offset(5000))); + $this->assertFalse($this->validator->isValid(Query::offset(-1))); + $this->assertFalse($this->validator->isValid(Query::offset(5001))); + $this->assertFalse($this->validator->isValid(Query::equal('dne', ['v']))); + $this->assertFalse($this->validator->isValid(Query::equal('', ['v']))); + $this->assertFalse($this->validator->isValid(Query::orderAsc('attr'))); + $this->assertFalse($this->validator->isValid(Query::orderDesc('attr'))); + $this->assertFalse($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf']))); + $this->assertFalse($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf']))); } } diff --git a/tests/Database/Validator/Query/LimitTest.php b/tests/Database/Validator/Query/LimitTest.php index 636e07fcc..a807a09cd 100644 --- a/tests/Database/Validator/Query/LimitTest.php +++ b/tests/Database/Validator/Query/LimitTest.php @@ -8,17 +8,22 @@ class LimitTest extends TestCase { - public function testValue(): void + public function testValueSuccess(): void { $validator = new Limit(100); - // Test for Success - $this->assertEquals($validator->isValid(Query::limit(1)), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::limit(100)), true, $validator->getDescription()); + $this->assertTrue($validator->isValid(Query::limit(1))); + $this->assertTrue($validator->isValid(Query::limit(100))); + } + + public function testValueFailure(): void + { + $validator = new Limit(100); - // Test for Failure - $this->assertEquals($validator->isValid(Query::limit(0)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::limit(-1)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::limit(101)), false, $validator->getDescription()); + $this->assertFalse($validator->isValid(Query::limit(0))); + $this->assertEquals('Invalid limit: Value must be a valid range between 1 and 100', $validator->getDescription()); + $this->assertFalse($validator->isValid(Query::limit(0))); + $this->assertFalse($validator->isValid(Query::limit(-1))); + $this->assertFalse($validator->isValid(Query::limit(101))); } } diff --git a/tests/Database/Validator/Query/OffsetTest.php b/tests/Database/Validator/Query/OffsetTest.php index 4f1ef7b5d..6f7e54b6d 100644 --- a/tests/Database/Validator/Query/OffsetTest.php +++ b/tests/Database/Validator/Query/OffsetTest.php @@ -8,21 +8,25 @@ class OffsetTest extends TestCase { - public function testValue(): void + public function testValueSuccess(): void { $validator = new Offset(5000); - // Test for Success - $this->assertEquals($validator->isValid(Query::offset(1)), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::offset(0)), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::offset(5000)), true, $validator->getDescription()); + $this->assertTrue($validator->isValid(Query::offset(1))); + $this->assertTrue($validator->isValid(Query::offset(0))); + $this->assertTrue($validator->isValid(Query::offset(5000))); + } + + public function testValueFailure(): void + { + $validator = new Offset(5000); - // Test for Failure - $this->assertEquals($validator->isValid(Query::offset(-1)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::offset(5001)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::equal('attr', ['v'])), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::orderAsc('attr')), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::orderDesc('attr')), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::limit(100)), false, $validator->getDescription()); + $this->assertFalse($validator->isValid(Query::offset(-1))); + $this->assertEquals('Invalid query', $validator->getDescription()); + $this->assertFalse($validator->isValid(Query::offset(5001))); + $this->assertFalse($validator->isValid(Query::equal('attr', ['v']))); + $this->assertFalse($validator->isValid(Query::orderAsc('attr'))); + $this->assertFalse($validator->isValid(Query::orderDesc('attr'))); + $this->assertFalse($validator->isValid(Query::limit(100))); } } diff --git a/tests/Database/Validator/Query/OrderTest.php b/tests/Database/Validator/Query/OrderTest.php index c0b914b12..a21ea9ac5 100644 --- a/tests/Database/Validator/Query/OrderTest.php +++ b/tests/Database/Validator/Query/OrderTest.php @@ -6,13 +6,16 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; +use Utopia\Database\Validator\Query\Base; use Utopia\Database\Validator\Query\Order; class OrderTest extends TestCase { - public function testValue(): void + protected Base|null $validator = null; + + public function setUp(): void { - $validator = new Order( + $this->validator = new Order( attributes: [ new Document([ '$id' => 'attr', @@ -22,22 +25,27 @@ public function testValue(): void ]), ], ); + } - // Test for Success - $this->assertEquals($validator->isValid(Query::orderAsc('attr')), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::orderAsc('')), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::orderDesc('attr')), true, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::orderDesc('')), true, $validator->getDescription()); + public function testValueSuccess(): void + { + $this->assertTrue($this->validator->isValid(Query::orderAsc('attr'))); + $this->assertTrue($this->validator->isValid(Query::orderAsc(''))); + $this->assertTrue($this->validator->isValid(Query::orderDesc('attr'))); + $this->assertTrue($this->validator->isValid(Query::orderDesc(''))); + } - // Test for Failure - $this->assertEquals($validator->isValid(Query::limit(-1)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::limit(101)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::offset(-1)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::offset(5001)), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::equal('attr', ['v'])), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::equal('dne', ['v'])), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::equal('', ['v'])), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::orderDesc('dne')), false, $validator->getDescription()); - $this->assertEquals($validator->isValid(Query::orderAsc('dne')), false, $validator->getDescription()); + public function testValueFailure(): void + { + $this->assertFalse($this->validator->isValid(Query::limit(-1))); + $this->assertEquals('xxx', $this->validator->getDescription()); + $this->assertFalse($this->validator->isValid(Query::limit(101))); + $this->assertFalse($this->validator->isValid(Query::offset(-1))); + $this->assertFalse($this->validator->isValid(Query::offset(5001))); + $this->assertFalse($this->validator->isValid(Query::equal('attr', ['v']))); + $this->assertFalse($this->validator->isValid(Query::equal('dne', ['v']))); + $this->assertFalse($this->validator->isValid(Query::equal('', ['v']))); + $this->assertFalse($this->validator->isValid(Query::orderDesc('dne'))); + $this->assertFalse($this->validator->isValid(Query::orderAsc('dne'))); } } diff --git a/tests/Database/Validator/Query/SelectTest.php b/tests/Database/Validator/Query/SelectTest.php index 341755712..9b94ae621 100644 --- a/tests/Database/Validator/Query/SelectTest.php +++ b/tests/Database/Validator/Query/SelectTest.php @@ -6,13 +6,16 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; +use Utopia\Database\Validator\Query\Base; use Utopia\Database\Validator\Query\Select; class SelectTest extends TestCase { - public function testValue(): void + protected Base|null $validator = null; + + public function setUp(): void { - $validator = new Select( + $this->validator = new Select( attributes: [ new Document([ '$id' => 'attr', @@ -22,11 +25,16 @@ public function testValue(): void ]), ], ); + } - // Test for Success - $this->assertEquals($validator->isValid(Query::select(['*', 'attr'])), true, $validator->getDescription()); + public function testValueSuccess(): void + { + $this->assertTrue($this->validator->isValid(Query::select(['*', 'attr']))); + } - // Test for Failure - $this->assertEquals($validator->isValid(Query::limit(1)), false, $validator->getDescription()); + public function testValueFailure(): void + { + $this->assertFalse($this->validator->isValid(Query::limit(1))); + $this->assertEquals('bla', $this->validator->getDescription()); } } From 3a6fd3a0fd46bc1b5cbe4733e8176e1ae78cf27b Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 21 May 2023 13:36:52 +0300 Subject: [PATCH 58/75] Fix OffsetTest.php test --- tests/Database/Validator/Query/OffsetTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Database/Validator/Query/OffsetTest.php b/tests/Database/Validator/Query/OffsetTest.php index 6f7e54b6d..81d1d60c5 100644 --- a/tests/Database/Validator/Query/OffsetTest.php +++ b/tests/Database/Validator/Query/OffsetTest.php @@ -22,7 +22,7 @@ public function testValueFailure(): void $validator = new Offset(5000); $this->assertFalse($validator->isValid(Query::offset(-1))); - $this->assertEquals('Invalid query', $validator->getDescription()); + $this->assertEquals('Invalid offset: Value must be a valid range between 0 and 5,000', $validator->getDescription()); $this->assertFalse($validator->isValid(Query::offset(5001))); $this->assertFalse($validator->isValid(Query::equal('attr', ['v']))); $this->assertFalse($validator->isValid(Query::orderAsc('attr'))); From e379b03c84d2e5bbdf94901df41e96fb957f687c Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 21 May 2023 14:10:46 +0300 Subject: [PATCH 59/75] Some more tests --- tests/Database/Validator/Query/OrderTest.php | 2 +- tests/Database/Validator/Query/SelectTest.php | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/Database/Validator/Query/OrderTest.php b/tests/Database/Validator/Query/OrderTest.php index a21ea9ac5..c7a0d7826 100644 --- a/tests/Database/Validator/Query/OrderTest.php +++ b/tests/Database/Validator/Query/OrderTest.php @@ -38,7 +38,7 @@ public function testValueSuccess(): void public function testValueFailure(): void { $this->assertFalse($this->validator->isValid(Query::limit(-1))); - $this->assertEquals('xxx', $this->validator->getDescription()); + $this->assertEquals('Invalid query', $this->validator->getDescription()); $this->assertFalse($this->validator->isValid(Query::limit(101))); $this->assertFalse($this->validator->isValid(Query::offset(-1))); $this->assertFalse($this->validator->isValid(Query::offset(5001))); diff --git a/tests/Database/Validator/Query/SelectTest.php b/tests/Database/Validator/Query/SelectTest.php index 9b94ae621..a4972bbed 100644 --- a/tests/Database/Validator/Query/SelectTest.php +++ b/tests/Database/Validator/Query/SelectTest.php @@ -23,6 +23,12 @@ public function setUp(): void 'type' => Database::VAR_STRING, 'array' => false, ]), + new Document([ + '$id' => 'artist', + 'key' => 'artist', + 'type' => Database::VAR_RELATIONSHIP, + 'array' => false, + ]), ], ); } @@ -30,11 +36,13 @@ public function setUp(): void public function testValueSuccess(): void { $this->assertTrue($this->validator->isValid(Query::select(['*', 'attr']))); + $this->assertTrue($this->validator->isValid(Query::select(['artist.name']))); } public function testValueFailure(): void { $this->assertFalse($this->validator->isValid(Query::limit(1))); - $this->assertEquals('bla', $this->validator->getDescription()); + $this->assertEquals('Invalid query', $this->validator->getDescription()); + $this->assertFalse($this->validator->isValid(Query::select(['name.artist']))); } } From a4751bdd0d0f0758d17bd092119881d40d7825a4 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 21 May 2023 15:24:48 +0300 Subject: [PATCH 60/75] Filter parsing tests + Split empty operators --- src/Database/Validator/Query/Filter.php | 35 ++++++++++++++---- tests/Database/Validator/Query/FilterTest.php | 36 +++++++++++++++++++ 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index c37b69aba..136744f3e 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -104,6 +104,23 @@ protected function isValidAttributeAndValues(string $attribute, array $values): return true; } + /** + * @param array $values + * @return bool + */ + protected function isEmpty(array $values): bool + { + if (count($values) === 0) { + return true; + } + + if (is_array($values[0]) && count($values[0]) === 0) { + return true; + } + + return false; + } + /** * Is valid. * @@ -120,6 +137,16 @@ public function isValid($value): bool $attribute = $value->getAttribute(); switch ($method) { case Query::TYPE_EQUAL: + case Query::TYPE_CONTAINS: + $values = $value->getValues(); + + if ($this->isEmpty($values)) { + $this->message = \ucfirst($method) . ' queries require at least one value.'; + return false; + } + + return $this->isValidAttributeAndValues($attribute, $values); + case Query::TYPE_NOTEQUAL: case Query::TYPE_LESSER: case Query::TYPE_LESSEREQUAL: @@ -129,15 +156,9 @@ public function isValid($value): bool case Query::TYPE_STARTS_WITH: case Query::TYPE_ENDS_WITH: case Query::TYPE_BETWEEN: - case Query::TYPE_CONTAINS: $values = $value->getValues(); - if (count($values) === 0) { - $this->message = \ucfirst($method) . ' queries require at least one value.'; - return false; - } - - if (is_array($values[0]) && count($values[0]) === 0) { + if ($this->isEmpty($values)) { $this->message = \ucfirst($method) . ' can take only one value.'; return false; } diff --git a/tests/Database/Validator/Query/FilterTest.php b/tests/Database/Validator/Query/FilterTest.php index 0537c8888..4bcc2f2a0 100644 --- a/tests/Database/Validator/Query/FilterTest.php +++ b/tests/Database/Validator/Query/FilterTest.php @@ -57,4 +57,40 @@ public function testFailure(): void $this->assertFalse($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf']))); $this->assertFalse($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf']))); } + + public function testTypeMissmatch(): void + { + $this->assertFalse($this->validator->isValid(Query::parse('equal("attr", false)'))); + $this->assertEquals('Query type does not match expected: string', $this->validator->getDescription()); + + $this->assertFalse($this->validator->isValid(Query::parse('equal("attr", null)'))); + $this->assertEquals('Query type does not match expected: string', $this->validator->getDescription()); + } + + public function testEmptyValues(): void + { + $this->assertFalse($this->validator->isValid(Query::parse('notEqual("attr", [])'))); + $this->assertEquals('NotEqual can take only one value.', $this->validator->getDescription()); + + $this->assertFalse($this->validator->isValid(Query::parse('contains("attr", [])'))); + $this->assertEquals('Contains queries require at least one value.', $this->validator->getDescription()); + + $this->assertFalse($this->validator->isValid(Query::parse('equal("attr", [])'))); + $this->assertEquals('Equal queries require at least one value.', $this->validator->getDescription()); + + $this->assertFalse($this->validator->isValid(Query::parse('lessThan("attr", [])'))); + $this->assertEquals('LessThan can take only one value.', $this->validator->getDescription()); + + $this->assertFalse($this->validator->isValid(Query::parse('lessThanEqual("attr", [])'))); + $this->assertEquals('LessThanEqual can take only one value.', $this->validator->getDescription()); + + $this->assertFalse($this->validator->isValid(Query::parse('search("attr", [])'))); + $this->assertEquals('Search can take only one value.', $this->validator->getDescription()); + + $this->assertFalse($this->validator->isValid(Query::parse('greaterThanEqual("attr", [])'))); + $this->assertEquals('GreaterThanEqual can take only one value.', $this->validator->getDescription()); + + $this->assertFalse($this->validator->isValid(Query::parse('greaterThan("attr", [])'))); + $this->assertEquals('GreaterThan can take only one value.', $this->validator->getDescription()); + } } From 817c60d8595465d4d4446c603d13f10e0f6b0c17 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 21 May 2023 15:25:38 +0300 Subject: [PATCH 61/75] stopOnFailure --- phpunit.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 3833748e0..31b947dd6 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true"> + stopOnFailure="false"> ./tests/ From 0db9f8d37300905c387e24cc7f7d2e01fd743d44 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 22 May 2023 09:28:03 +0300 Subject: [PATCH 62/75] lint changes for $schema --- src/Database/Validator/Query/Filter.php | 2 +- src/Database/Validator/Query/Order.php | 2 +- src/Database/Validator/Query/Select.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index 136744f3e..a4d64aba6 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -9,7 +9,7 @@ class Filter extends Base { /** - * @var array> + * @var array */ protected array $schema = []; diff --git a/src/Database/Validator/Query/Order.php b/src/Database/Validator/Query/Order.php index 46e08f7e2..40ef707d4 100644 --- a/src/Database/Validator/Query/Order.php +++ b/src/Database/Validator/Query/Order.php @@ -8,7 +8,7 @@ class Order extends Base { /** - * @var array> + * @var array */ protected array $schema = []; diff --git a/src/Database/Validator/Query/Select.php b/src/Database/Validator/Query/Select.php index 78b64a5d2..de20fc138 100644 --- a/src/Database/Validator/Query/Select.php +++ b/src/Database/Validator/Query/Select.php @@ -8,7 +8,7 @@ class Select extends Base { /** - * @var array> + * @var array */ protected array $schema = []; From 0792731028f30032bb47abc4ff26170a6d505fb8 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 22 May 2023 09:29:58 +0300 Subject: [PATCH 63/75] Invalid query --- src/Database/Validator/Queries.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index d3d6d2317..80a11f8e5 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -56,7 +56,7 @@ public function isValid($value): bool try { $query = Query::parse($query); } catch (\Throwable $e) { - $this->message = $e->getMessage(); + $this->message = 'Invalid query: ' . $e->getMessage(); return false; } } From 6b50f76bc30339fd177c6c359faf85b0a9ea8d5a Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 22 May 2023 15:23:38 +0300 Subject: [PATCH 64/75] Merge tracking branch --- composer.lock | 60 ++++++++++++------------- src/Database/Validator/Queries.php | 6 +-- src/Database/Validator/Query/Filter.php | 6 +-- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/composer.lock b/composer.lock index c833faf44..ff525b0a8 100644 --- a/composer.lock +++ b/composer.lock @@ -512,16 +512,16 @@ }, { "name": "fakerphp/faker", - "version": "v1.21.0", + "version": "v1.22.0", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "92efad6a967f0b79c499705c69b662f738cc9e4d" + "reference": "f85772abd508bd04e20bb4b1bbe260a68d0066d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/92efad6a967f0b79c499705c69b662f738cc9e4d", - "reference": "92efad6a967f0b79c499705c69b662f738cc9e4d", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/f85772abd508bd04e20bb4b1bbe260a68d0066d2", + "reference": "f85772abd508bd04e20bb4b1bbe260a68d0066d2", "shasum": "" }, "require": { @@ -574,9 +574,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.21.0" + "source": "https://github.com/FakerPHP/Faker/tree/v1.22.0" }, - "time": "2022-12-13T13:54:32+00:00" + "time": "2023-05-14T12:31:37+00:00" }, { "name": "laravel/pint", @@ -705,16 +705,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.15.4", + "version": "v4.15.5", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" + "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e", + "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e", "shasum": "" }, "require": { @@ -755,9 +755,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5" }, - "time": "2023-03-05T19:49:14+00:00" + "time": "2023-05-19T20:20:00+00:00" }, { "name": "pcov/clobber", @@ -906,16 +906,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.14", + "version": "1.10.15", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d232901b09e67538e5c86a724be841bea5768a7c" + "reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d232901b09e67538e5c86a724be841bea5768a7c", - "reference": "d232901b09e67538e5c86a724be841bea5768a7c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/762c4dac4da6f8756eebb80e528c3a47855da9bd", + "reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd", "shasum": "" }, "require": { @@ -964,7 +964,7 @@ "type": "tidelift" } ], - "time": "2023-04-19T13:47:27+00:00" + "time": "2023-05-09T15:28:01+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1286,16 +1286,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.7", + "version": "9.6.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2" + "reference": "17d621b3aff84d0c8b62539e269e87d8d5baa76e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", - "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/17d621b3aff84d0c8b62539e269e87d8d5baa76e", + "reference": "17d621b3aff84d0c8b62539e269e87d8d5baa76e", "shasum": "" }, "require": { @@ -1369,7 +1369,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.8" }, "funding": [ { @@ -1385,7 +1385,7 @@ "type": "tidelift" } ], - "time": "2023-04-14T08:58:40+00:00" + "time": "2023-05-11T05:14:45+00:00" }, { "name": "psr/container", @@ -1786,16 +1786,16 @@ }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", "shasum": "" }, "require": { @@ -1840,7 +1840,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" }, "funding": [ { @@ -1848,7 +1848,7 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2023-05-07T05:35:17+00:00" }, { "name": "sebastian/environment", @@ -2673,5 +2673,5 @@ "php": ">=8.0" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index 80a11f8e5..309ba0cc8 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -71,11 +71,11 @@ public function isValid($value): bool Query::TYPE_ORDERASC, Query::TYPE_ORDERDESC => Base::METHOD_TYPE_ORDER, Query::TYPE_EQUAL, - Query::TYPE_NOTEQUAL, + Query::TYPE_NOT_EQUAL, Query::TYPE_LESSER, - Query::TYPE_LESSEREQUAL, + Query::TYPE_LESSER_EQUAL, Query::TYPE_GREATER, - Query::TYPE_GREATEREQUAL, + Query::TYPE_GREATER_EQUAL, Query::TYPE_SEARCH, Query::TYPE_IS_NULL, Query::TYPE_IS_NOT_NULL, diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index a4d64aba6..b61def78f 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -147,11 +147,11 @@ public function isValid($value): bool return $this->isValidAttributeAndValues($attribute, $values); - case Query::TYPE_NOTEQUAL: + case Query::TYPE_NOT_EQUAL: case Query::TYPE_LESSER: - case Query::TYPE_LESSEREQUAL: + case Query::TYPE_LESSER_EQUAL: case Query::TYPE_GREATER: - case Query::TYPE_GREATEREQUAL: + case Query::TYPE_GREATER_EQUAL: case Query::TYPE_SEARCH: case Query::TYPE_STARTS_WITH: case Query::TYPE_ENDS_WITH: From 3a8b8618e7fd03c0f24ce161a25a8a2c5a064c3c Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 28 May 2023 16:55:28 +0300 Subject: [PATCH 65/75] Invalid query text change --- src/Database/Validator/Queries.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index 309ba0cc8..be79ccb7f 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -92,7 +92,7 @@ public function isValid($value): bool continue; } if (!$validator->isValid($query)) { - $this->message = 'Query not valid: ' . $validator->getDescription(); + $this->message = 'Invalid query: ' . $validator->getDescription(); return false; } From e4c1ec1c9b134385aa930add264319f167cce7fc Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 28 May 2023 17:02:42 +0300 Subject: [PATCH 66/75] Invalid query changes --- phpunit.xml | 2 +- src/Database/Validator/Queries.php | 2 +- src/Database/Validator/Queries/Document.php | 7 +++---- tests/Database/Base.php | 16 ++++++++-------- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index 31b947dd6..3833748e0 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false"> + stopOnFailure="true"> ./tests/ diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index be79ccb7f..c80a18366 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -100,7 +100,7 @@ public function isValid($value): bool } if (!$methodIsValid) { - $this->message = 'Query method not valid: ' . $method; + $this->message = 'Invalid query method: ' . $method; return false; } } diff --git a/src/Database/Validator/Queries/Document.php b/src/Database/Validator/Queries/Document.php index f62fb8bbf..fbb164d9d 100644 --- a/src/Database/Validator/Queries/Document.php +++ b/src/Database/Validator/Queries/Document.php @@ -6,7 +6,6 @@ use Utopia\Database\Database; use Utopia\Database\Validator\Queries; use Utopia\Database\Validator\Query\Select; -use Utopia\Database\Document as DocumentValidator; class Document extends Queries { @@ -16,21 +15,21 @@ class Document extends Queries */ public function __construct(array $attributes) { - $attributes[] = new DocumentValidator([ + $attributes[] = new \Utopia\Database\Document([ '$id' => '$id', 'key' => '$id', 'type' => Database::VAR_STRING, 'array' => false, ]); - $attributes[] = new DocumentValidator([ + $attributes[] = new \Utopia\Database\Document([ '$id' => '$createdAt', 'key' => '$createdAt', 'type' => Database::VAR_DATETIME, 'array' => false, ]); - $attributes[] = new DocumentValidator([ + $attributes[] = new \Utopia\Database\Document([ '$id' => '$updatedAt', 'key' => '$updatedAt', 'type' => Database::VAR_DATETIME, diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 393dcbe82..212bd743a 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -11079,7 +11079,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: Equal queries require at least one value.', $e->getMessage()); + $this->assertEquals('Invalid query: Equal queries require at least one value.', $e->getMessage()); } try { @@ -11089,7 +11089,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: Query type does not match expected: string', $e->getMessage()); + $this->assertEquals('Invalid query: Query type does not match expected: string', $e->getMessage()); } try { @@ -11099,7 +11099,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: NotEqual can take only one value.', $e->getMessage()); + $this->assertEquals('Invalid query: NotEqual can take only one value.', $e->getMessage()); } try { @@ -11109,7 +11109,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: LessThan can take only one value.', $e->getMessage()); + $this->assertEquals('Invalid query: LessThan can take only one value.', $e->getMessage()); } try { @@ -11119,7 +11119,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: LessThanEqual can take only one value.', $e->getMessage()); + $this->assertEquals('Invalid query: LessThanEqual can take only one value.', $e->getMessage()); } try { @@ -11129,7 +11129,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: GreaterThan can take only one value.', $e->getMessage()); + $this->assertEquals('Invalid query: GreaterThan can take only one value.', $e->getMessage()); } try { @@ -11139,7 +11139,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: GreaterThanEqual can take only one value.', $e->getMessage()); + $this->assertEquals('Invalid query: GreaterThanEqual can take only one value.', $e->getMessage()); } try { @@ -11149,7 +11149,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Query not valid: Contains queries require at least one value.', $e->getMessage()); + $this->assertEquals('Invalid query: Contains queries require at least one value.', $e->getMessage()); } } From c488929d5f4406466e3a0e367a1dd4cd103d93b6 Mon Sep 17 00:00:00 2001 From: Shmuel Fogel Date: Sun, 28 May 2023 17:03:08 +0300 Subject: [PATCH 67/75] Update src/Database/Validator/Query/Filter.php Co-authored-by: Jake Barnby --- src/Database/Validator/Query/Filter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index b61def78f..320a5716d 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -159,7 +159,7 @@ public function isValid($value): bool $values = $value->getValues(); if ($this->isEmpty($values)) { - $this->message = \ucfirst($method) . ' can take only one value.'; + $this->message = \ucfirst($method) . ' queries require exactly one value.'; return false; } From 6da27fcbd0395c4755d3dbac591c6c50c89048e4 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 28 May 2023 17:26:37 +0300 Subject: [PATCH 68/75] Invalid query changes --- src/Database/Validator/Query/Filter.php | 21 +++++++++++-------- .../Validator/DocumentsQueriesTest.php | 9 ++++---- tests/Database/Validator/QueryTest.php | 2 +- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index b61def78f..2ba3d2b19 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -138,14 +138,12 @@ public function isValid($value): bool switch ($method) { case Query::TYPE_EQUAL: case Query::TYPE_CONTAINS: - $values = $value->getValues(); - - if ($this->isEmpty($values)) { + if ($this->isEmpty($value->getValues())) { $this->message = \ucfirst($method) . ' queries require at least one value.'; return false; } - return $this->isValidAttributeAndValues($attribute, $values); + return $this->isValidAttributeAndValues($attribute, $value->getValues()); case Query::TYPE_NOT_EQUAL: case Query::TYPE_LESSER: @@ -155,15 +153,20 @@ public function isValid($value): bool case Query::TYPE_SEARCH: case Query::TYPE_STARTS_WITH: case Query::TYPE_ENDS_WITH: - case Query::TYPE_BETWEEN: - $values = $value->getValues(); - - if ($this->isEmpty($values)) { + if ($this->isEmpty($value->getValues())) { $this->message = \ucfirst($method) . ' can take only one value.'; return false; } - return $this->isValidAttributeAndValues($attribute, $values); + return $this->isValidAttributeAndValues($attribute, $value->getValues()); + + case Query::TYPE_BETWEEN: + if (count($value->getValues()) != 2) { + $this->message = \ucfirst($method) . ' can take only two value.'; + return false; + } + + return $this->isValidAttributeAndValues($attribute, $value->getValues()); case Query::TYPE_IS_NULL: case Query::TYPE_IS_NOT_NULL: diff --git a/tests/Database/Validator/DocumentsQueriesTest.php b/tests/Database/Validator/DocumentsQueriesTest.php index 289a80bdb..03c7b989c 100644 --- a/tests/Database/Validator/DocumentsQueriesTest.php +++ b/tests/Database/Validator/DocumentsQueriesTest.php @@ -155,10 +155,9 @@ public function testInvalidQueries(): void $this->assertEquals(false, $validator->isValid($queries)); $this->assertEquals('Searching by attribute "description" requires a fulltext index.', $validator->getDescription()); - $queries = ['equal("not_found", 4)']; $this->assertEquals(false, $validator->isValid($queries)); - $this->assertEquals('Query not valid: Attribute not found in schema: not_found', $validator->getDescription()); + $this->assertEquals('Invalid query: Attribute not found in schema: not_found', $validator->getDescription()); $queries = ['search("description", "iron")']; $this->assertEquals(false, $validator->isValid($queries)); @@ -166,14 +165,14 @@ public function testInvalidQueries(): void $queries = ['equal("not_found", 4)']; $this->assertEquals(false, $validator->isValid($queries)); - $this->assertEquals('Query not valid: Attribute not found in schema: not_found', $validator->getDescription()); + $this->assertEquals('Invalid query: Attribute not found in schema: not_found', $validator->getDescription()); $queries = ['limit(-1)']; $this->assertEquals(false, $validator->isValid($queries)); - $this->assertEquals('Query not valid: Invalid limit: Value must be a valid range between 1 and 9,223,372,036,854,775,808', $validator->getDescription()); + $this->assertEquals('Invalid query: Invalid limit: Value must be a valid range between 1 and 9,223,372,036,854,775,808', $validator->getDescription()); $queries = ['equal("title", [])']; // empty array $this->assertEquals(false, $validator->isValid($queries)); - $this->assertEquals('Query not valid: Equal queries require at least one value.', $validator->getDescription()); + $this->assertEquals('Invalid query: Equal queries require at least one value.', $validator->getDescription()); } } diff --git a/tests/Database/Validator/QueryTest.php b/tests/Database/Validator/QueryTest.php index e77e663b0..2dd2c484f 100644 --- a/tests/Database/Validator/QueryTest.php +++ b/tests/Database/Validator/QueryTest.php @@ -140,7 +140,7 @@ public function testInvalidMethod(): void $validator = new Documents($this->attributes, []); $this->assertEquals(false, $validator->isValid([Query::parse('eqqual("title", "Iron Man")')])); - $this->assertEquals('Query method not valid: eqqual', $validator->getDescription()); + $this->assertEquals('Invalid query method: eqqual', $validator->getDescription()); } /** From c9043dbfdc3ccaf3faad38f7ea4f2bc6833eda1a Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 28 May 2023 19:07:54 +0300 Subject: [PATCH 69/75] Filter operators count = 1 --- src/Database/Validator/Query/Filter.php | 2 +- src/Database/Validator/Query/Limit.php | 2 +- tests/Database/Base.php | 10 +++++----- tests/Database/Validator/DocumentsQueriesTest.php | 4 +++- tests/Database/Validator/Query/FilterTest.php | 12 ++++++------ tests/Database/Validator/QueryTest.php | 11 +++++++---- 6 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index aa0161870..0c85da8db 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -153,7 +153,7 @@ public function isValid($value): bool case Query::TYPE_SEARCH: case Query::TYPE_STARTS_WITH: case Query::TYPE_ENDS_WITH: - if ($this->isEmpty($value->getValues())) { + if (count($value->getValues()) != 1) { $this->message = \ucfirst($method) . ' queries require exactly one value.'; return false; } diff --git a/src/Database/Validator/Query/Limit.php b/src/Database/Validator/Query/Limit.php index 11d25d351..facc266d7 100644 --- a/src/Database/Validator/Query/Limit.php +++ b/src/Database/Validator/Query/Limit.php @@ -35,7 +35,7 @@ public function isValid($value): bool } if ($value->getMethod() !== Query::TYPE_LIMIT) { - $this->message = 'Query method invalid: ' . $value->getMethod(); + $this->message = 'Invalid query method: ' . $value->getMethod(); return false; } diff --git a/tests/Database/Base.php b/tests/Database/Base.php index 212bd743a..17a2bbc05 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -11099,7 +11099,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Invalid query: NotEqual can take only one value.', $e->getMessage()); + $this->assertEquals('Invalid query: Query type does not match expected: string', $e->getMessage()); } try { @@ -11109,7 +11109,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Invalid query: LessThan can take only one value.', $e->getMessage()); + $this->assertEquals('Invalid query: Query type does not match expected: string', $e->getMessage()); } try { @@ -11119,7 +11119,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Invalid query: LessThanEqual can take only one value.', $e->getMessage()); + $this->assertEquals('Invalid query: Query type does not match expected: string', $e->getMessage()); } try { @@ -11129,7 +11129,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Invalid query: GreaterThan can take only one value.', $e->getMessage()); + $this->assertEquals('Invalid query: Query type does not match expected: string', $e->getMessage()); } try { @@ -11139,7 +11139,7 @@ public function testEmptyOperatorValues(): void $this->fail('Failed to throw exception'); } catch (Exception $e) { $this->assertInstanceOf(Exception::class, $e); - $this->assertEquals('Invalid query: GreaterThanEqual can take only one value.', $e->getMessage()); + $this->assertEquals('Invalid query: Query type does not match expected: string', $e->getMessage()); } try { diff --git a/tests/Database/Validator/DocumentsQueriesTest.php b/tests/Database/Validator/DocumentsQueriesTest.php index 03c7b989c..65670982a 100644 --- a/tests/Database/Validator/DocumentsQueriesTest.php +++ b/tests/Database/Validator/DocumentsQueriesTest.php @@ -116,7 +116,6 @@ public function testValidQueries(): void $validator = new Documents($this->collection['attributes'], $this->collection['indexes']); $queries = [ - 'notEqual("title", ["Iron Man", "Ant Man"])', 'equal("description", "Best movie ever")', 'equal("description", [""])', 'lessThanEqual("price", 6.50)', @@ -151,6 +150,9 @@ public function testInvalidQueries(): void { $validator = new Documents($this->collection['attributes'], $this->collection['indexes']); + $this->assertEquals(false, $validator->isValid(['notEqual("title", ["Iron Man", "Ant Man"])',])); + $this->assertEquals('Invalid query: NotEqual queries require exactly one value.', $validator->getDescription()); + $queries = ['search("description", "iron")']; $this->assertEquals(false, $validator->isValid($queries)); $this->assertEquals('Searching by attribute "description" requires a fulltext index.', $validator->getDescription()); diff --git a/tests/Database/Validator/Query/FilterTest.php b/tests/Database/Validator/Query/FilterTest.php index 4bcc2f2a0..752dd97a3 100644 --- a/tests/Database/Validator/Query/FilterTest.php +++ b/tests/Database/Validator/Query/FilterTest.php @@ -70,7 +70,7 @@ public function testTypeMissmatch(): void public function testEmptyValues(): void { $this->assertFalse($this->validator->isValid(Query::parse('notEqual("attr", [])'))); - $this->assertEquals('NotEqual can take only one value.', $this->validator->getDescription()); + $this->assertEquals('NotEqual queries require exactly one value.', $this->validator->getDescription()); $this->assertFalse($this->validator->isValid(Query::parse('contains("attr", [])'))); $this->assertEquals('Contains queries require at least one value.', $this->validator->getDescription()); @@ -79,18 +79,18 @@ public function testEmptyValues(): void $this->assertEquals('Equal queries require at least one value.', $this->validator->getDescription()); $this->assertFalse($this->validator->isValid(Query::parse('lessThan("attr", [])'))); - $this->assertEquals('LessThan can take only one value.', $this->validator->getDescription()); + $this->assertEquals('LessThan queries require exactly one value.', $this->validator->getDescription()); $this->assertFalse($this->validator->isValid(Query::parse('lessThanEqual("attr", [])'))); - $this->assertEquals('LessThanEqual can take only one value.', $this->validator->getDescription()); + $this->assertEquals('LessThanEqual queries require exactly one value.', $this->validator->getDescription()); $this->assertFalse($this->validator->isValid(Query::parse('search("attr", [])'))); - $this->assertEquals('Search can take only one value.', $this->validator->getDescription()); + $this->assertEquals('Search queries require exactly one value.', $this->validator->getDescription()); $this->assertFalse($this->validator->isValid(Query::parse('greaterThanEqual("attr", [])'))); - $this->assertEquals('GreaterThanEqual can take only one value.', $this->validator->getDescription()); + $this->assertEquals('GreaterThanEqual queries require exactly one value.', $this->validator->getDescription()); $this->assertFalse($this->validator->isValid(Query::parse('greaterThan("attr", [])'))); - $this->assertEquals('GreaterThan can take only one value.', $this->validator->getDescription()); + $this->assertEquals('GreaterThan queries require exactly one value.', $this->validator->getDescription()); } } diff --git a/tests/Database/Validator/QueryTest.php b/tests/Database/Validator/QueryTest.php index 2dd2c484f..b832e5a88 100644 --- a/tests/Database/Validator/QueryTest.php +++ b/tests/Database/Validator/QueryTest.php @@ -112,9 +112,9 @@ public function testQuery(): void $this->assertEquals(true, $validator->isValid([Query::parse('equal("$id", ["Iron Man", "Ant Man"])')])); $this->assertEquals(true, $validator->isValid([Query::parse('equal("$id", "Iron Man")')])); - $this->assertEquals(true, $validator->isValid([Query::parse('notEqual("title", ["Iron Man", "Ant Man"])')])); $this->assertEquals(true, $validator->isValid([Query::parse('equal("description", "Best movie ever")')])); $this->assertEquals(true, $validator->isValid([Query::parse('greaterThan("rating", 4)')])); + $this->assertEquals(true, $validator->isValid([Query::parse('notEqual("title", ["Iron Man"])')])); $this->assertEquals(true, $validator->isValid([Query::parse('lessThan("price", 6.50)')])); $this->assertEquals(true, $validator->isValid([Query::parse('lessThanEqual("price", 6)')])); $this->assertEquals(true, $validator->isValid([Query::parse('contains("tags", ["action1", "action2"])')])); @@ -141,6 +141,9 @@ public function testInvalidMethod(): void $this->assertEquals(false, $validator->isValid([Query::parse('eqqual("title", "Iron Man")')])); $this->assertEquals('Invalid query method: eqqual', $validator->getDescription()); + + $this->assertEquals(false, $validator->isValid([Query::parse('notEqual("title", ["Iron Man", "Ant Man"])')])); + $this->assertEquals(false, $validator->isValid([Query::parse('notEqual("title", [""])')])); } /** @@ -153,12 +156,12 @@ public function testAttributeNotFound(): void $response = $validator->isValid([Query::parse('equal("name", "Iron Man")')]); $this->assertEquals(false, $response); - $this->assertEquals('Query not valid: Attribute not found in schema: name', $validator->getDescription()); + $this->assertEquals('Invalid query: Attribute not found in schema: name', $validator->getDescription()); $response = $validator->isValid([Query::parse('orderAsc("name")')]); $this->assertEquals(false, $response); - $this->assertEquals('Query not valid: Attribute not found in schema: name', $validator->getDescription()); + $this->assertEquals('Invalid query: Attribute not found in schema: name', $validator->getDescription()); } /** @@ -171,7 +174,7 @@ public function testAttributeWrongType(): void $response = $validator->isValid([Query::parse('equal("title", 1776)')]); $this->assertEquals(false, $response); - $this->assertEquals('Query not valid: Query type does not match expected: string', $validator->getDescription()); + $this->assertEquals('Invalid query: Query type does not match expected: string', $validator->getDescription()); } /** From 75b2d09de50aeac3cc3766601638a3d262d547cb Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 28 May 2023 19:08:06 +0300 Subject: [PATCH 70/75] Php unit --- phpunit.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 3833748e0..31b947dd6 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true"> + stopOnFailure="false"> ./tests/ From 9bad321ec2d1d31261c89f52193fcc80f43d1f35 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 28 May 2023 19:17:22 +0300 Subject: [PATCH 71/75] notEqual empty string --- tests/Database/Validator/QueryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Database/Validator/QueryTest.php b/tests/Database/Validator/QueryTest.php index b832e5a88..76f78c1a5 100644 --- a/tests/Database/Validator/QueryTest.php +++ b/tests/Database/Validator/QueryTest.php @@ -130,6 +130,7 @@ public function testQuery(): void $this->assertEquals(true, $validator->isValid([Query::parse('startsWith("title", "Fro")')])); $this->assertEquals(true, $validator->isValid([Query::parse('endsWith("title", "Zen")')])); $this->assertEquals(true, $validator->isValid([Query::parse('select(["title", "description"])')])); + $this->assertEquals(true, $validator->isValid([Query::parse('notEqual("title", [""])')])); } /** @@ -143,7 +144,6 @@ public function testInvalidMethod(): void $this->assertEquals('Invalid query method: eqqual', $validator->getDescription()); $this->assertEquals(false, $validator->isValid([Query::parse('notEqual("title", ["Iron Man", "Ant Man"])')])); - $this->assertEquals(false, $validator->isValid([Query::parse('notEqual("title", [""])')])); } /** From f1382bbfbe93700fc35dc83a8dca3ae897fcf486 Mon Sep 17 00:00:00 2001 From: Shmuel Fogel Date: Mon, 29 May 2023 11:58:47 +0300 Subject: [PATCH 72/75] Update src/Database/Validator/Query/Filter.php Co-authored-by: Jake Barnby --- src/Database/Validator/Query/Filter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index 0c85da8db..9eab6b28e 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -162,7 +162,7 @@ public function isValid($value): bool case Query::TYPE_BETWEEN: if (count($value->getValues()) != 2) { - $this->message = \ucfirst($method) . ' can take only two value.'; + $this->message = \ucfirst($method) . ' queries require exactly two values.'; return false; } From 485d6b46183811a26f456b2f66aa50838177acd7 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 29 May 2023 12:00:08 +0300 Subject: [PATCH 73/75] PHP_INT_MAX --- tests/Database/Validator/DocumentsQueriesTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Database/Validator/DocumentsQueriesTest.php b/tests/Database/Validator/DocumentsQueriesTest.php index 65670982a..e928e6785 100644 --- a/tests/Database/Validator/DocumentsQueriesTest.php +++ b/tests/Database/Validator/DocumentsQueriesTest.php @@ -171,7 +171,7 @@ public function testInvalidQueries(): void $queries = ['limit(-1)']; $this->assertEquals(false, $validator->isValid($queries)); - $this->assertEquals('Invalid query: Invalid limit: Value must be a valid range between 1 and 9,223,372,036,854,775,808', $validator->getDescription()); + $this->assertEquals('Invalid query: Invalid limit: Value must be a valid range between 1 and ' . number_format(PHP_INT_MAX), $validator->getDescription()); $queries = ['equal("title", [])']; // empty array $this->assertEquals(false, $validator->isValid($queries)); From 98bd452fc7518fe3650c2ff07200859fb8d17d8a Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 29 May 2023 12:53:09 +0300 Subject: [PATCH 74/75] testTwoAttributesFulltext --- .../Database/Validator/IndexedQueriesTest.php | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/Database/Validator/IndexedQueriesTest.php b/tests/Database/Validator/IndexedQueriesTest.php index 2a4396e5e..fa166f37a 100644 --- a/tests/Database/Validator/IndexedQueriesTest.php +++ b/tests/Database/Validator/IndexedQueriesTest.php @@ -133,4 +133,44 @@ public function testMissingIndex(): void $this->assertEquals(false, $validator->isValid(['orderAsc("dne")']), $validator->getDescription()); $this->assertEquals(false, $validator->isValid(['search("name", "value")']), $validator->getDescription()); } + + public function testTwoAttributesFulltext(): void + { + $attributes = [ + new Document([ + '$id' => 'ft1', + 'key' => 'ft1', + 'type' => Database::VAR_STRING, + 'array' => false, + ]), + new Document([ + '$id' => 'ft2', + 'key' => 'ft2', + 'type' => Database::VAR_STRING, + 'array' => false, + ]), + ]; + + $indexes = [ + new Document([ + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['ft1','ft2'], + ]), + ]; + + $validator = new IndexedQueries( + $attributes, + $indexes, + [ + new Cursor(), + new Filter($attributes), + new Limit(), + new Offset(), + new Order($attributes) + ] + ); + + $this->assertEquals(false, $validator->isValid([Query::search('ft1', 'value')])); + } + } From 0a1c82720088da086073a4b69c337f17582f3abf Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 30 May 2023 15:21:27 +0300 Subject: [PATCH 75/75] formatting --- composer.lock | 15 ++++++++------- tests/Database/Validator/IndexedQueriesTest.php | 1 - 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index ff525b0a8..05ea6441e 100644 --- a/composer.lock +++ b/composer.lock @@ -336,23 +336,24 @@ }, { "name": "utopia-php/framework", - "version": "0.28.1", + "version": "0.28.2", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "7f22c556fc5991e54e5811a68fb39809b21bda55" + "reference": "bc0144ff3983afa6724c43f2ce578fdbceec21f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/7f22c556fc5991e54e5811a68fb39809b21bda55", - "reference": "7f22c556fc5991e54e5811a68fb39809b21bda55", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/bc0144ff3983afa6724c43f2ce578fdbceec21f9", + "reference": "bc0144ff3983afa6724c43f2ce578fdbceec21f9", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": ">=8.0" }, "require-dev": { "laravel/pint": "^1.2", + "phpstan/phpstan": "1.9.x-dev", "phpunit/phpunit": "^9.5.25", "vimeo/psalm": "4.27.0" }, @@ -374,9 +375,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.28.1" + "source": "https://github.com/utopia-php/framework/tree/0.28.2" }, - "time": "2023-03-02T08:16:01+00:00" + "time": "2023-05-30T06:47:57+00:00" }, { "name": "utopia-php/mongo", diff --git a/tests/Database/Validator/IndexedQueriesTest.php b/tests/Database/Validator/IndexedQueriesTest.php index fa166f37a..2e5b5893d 100644 --- a/tests/Database/Validator/IndexedQueriesTest.php +++ b/tests/Database/Validator/IndexedQueriesTest.php @@ -172,5 +172,4 @@ public function testTwoAttributesFulltext(): void $this->assertEquals(false, $validator->isValid([Query::search('ft1', 'value')])); } - }