diff --git a/composer.json b/composer.json index c28e846a0..429d1e558 100755 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ }, "require": { "ext-pdo": "*", + "ext-mbstring": "*", "php": ">=8.0", "utopia-php/framework": "0.*.*", "utopia-php/cache": "0.8.*", diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index 22f3976b3..decfd7db4 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -671,7 +671,7 @@ abstract public function getMaxIndexLength(): int; public static function setTimeout(int $milliseconds): void { if ($milliseconds <= 0) { - throw new Exception('Timeout must be greater than 0'); + throw new DatabaseException('Timeout must be greater than 0'); } self::$timeout = $milliseconds; } diff --git a/src/Database/Database.php b/src/Database/Database.php index ae904981c..e5f48e33a 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -12,11 +12,13 @@ use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\Restricted as RestrictedException; use Utopia\Database\Exception\Structure as StructureException; +use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\Authorization; -use Utopia\Database\Validator\Queries\Documents; +use Utopia\Database\Validator\Queries\Document as DocumentValidator; +use Utopia\Database\Validator\Queries\Documents as DocumentsValidator; use Utopia\Database\Validator\Index as IndexValidator; use Utopia\Database\Validator\Permissions; use Utopia\Database\Validator\Structure; @@ -2177,6 +2179,17 @@ public function getDocument(string $collection, string $id, array $queries = []) $collection = $this->silent(fn () => $this->getCollection($collection)); + if ($collection->isEmpty()) { + throw new DatabaseException('Collection not found'); + } + + $attributes = $collection->getAttribute('attributes', []); + + $validator = new DocumentValidator($attributes); + if (!$validator->isValid($queries)) { + throw new QueryException($validator->getDescription()); + } + $relationships = \array_filter( $collection->getAttribute('attributes', []), fn (Document $attribute) => $attribute->getAttribute('type') === self::VAR_RELATIONSHIP @@ -4043,9 +4056,9 @@ public function find(string $collection, array $queries = [], ?int $timeout = nu $attributes = $collection->getAttribute('attributes', []); $indexes = $collection->getAttribute('indexes', []); - $validator = new Documents($attributes, $indexes); + $validator = new DocumentsValidator($attributes, $indexes); if (!$validator->isValid($queries)) { - throw new Exception($validator->getDescription()); + throw new QueryException($validator->getDescription()); } $authorization = new Authorization(self::PERMISSION_READ); @@ -4156,8 +4169,6 @@ public function find(string $collection, array $queries = [], ?int $timeout = nu } } - $results = $this->applyNestedQueries($results, $nestedQueries, $relationships); - // Remove internal attributes which are not queried foreach ($queries as $query) { if ($query->getMethod() === Query::TYPE_SELECT) { @@ -4177,112 +4188,6 @@ public function find(string $collection, array $queries = [], ?int $timeout = nu return $results; } - /** - * @param array $results - * @param array $queries - * @param array $relationships - * @return array - */ - private function applyNestedQueries(array $results, array $queries, array $relationships): array - { - foreach ($results as $index => &$node) { - foreach ($queries as $query) { - $path = \explode('.', $query->getAttribute()); - - if (\count($path) == 1) { - continue; - } - - $matched = false; - foreach ($relationships as $relationship) { - if ($relationship->getId() === $path[0]) { - $matched = true; - break; - } - } - - if (!$matched) { - continue; - } - - $value = $node->getAttribute($path[0]); - - $levels = \count($path); - for ($i = 1; $i < $levels; $i++) { - if ($value instanceof Document) { - $value = $value->getAttribute($path[$i]); - } - } - - if (\is_array($value)) { - $values = \array_map(function ($value) use ($path, $levels) { - return $value[$path[$levels - 1]]; - }, $value); - } else { - $values = [$value]; - } - - $matched = false; - foreach ($values as $value) { - switch ($query->getMethod()) { - case Query::TYPE_EQUAL: - foreach ($query->getValues() as $queryValue) { - if ($value === $queryValue) { - $matched = true; - break 2; - } - } - break; - case Query::TYPE_NOT_EQUAL: - $matched = $value !== $query->getValue(); - break; - case Query::TYPE_GREATER: - $matched = $value > $query->getValue(); - break; - case Query::TYPE_GREATER_EQUAL: - $matched = $value >= $query->getValue(); - break; - case Query::TYPE_LESSER: - $matched = $value < $query->getValue(); - break; - case Query::TYPE_LESSER_EQUAL: - $matched = $value <= $query->getValue(); - break; - case Query::TYPE_CONTAINS: - $matched = \in_array($query->getValue(), $value); - break; - case Query::TYPE_SEARCH: - $matched = \str_contains($value, $query->getValue()); - break; - case Query::TYPE_IS_NULL: - $matched = $value === null; - break; - case Query::TYPE_IS_NOT_NULL: - $matched = $value !== null; - break; - case Query::TYPE_BETWEEN: - $matched = $value >= $query->getValues()[0] && $value <= $query->getValues()[1]; - break; - case Query::TYPE_STARTS_WITH: - $matched = \str_starts_with($value, $query->getValue()); - break; - case Query::TYPE_ENDS_WITH: - $matched = \str_ends_with($value, $query->getValue()); - break; - default: - break; - } - } - - if (!$matched) { - unset($results[$index]); - } - } - } - - return \array_values($results); - } - /** * @param string $collection * @param array $queries @@ -4291,7 +4196,10 @@ private function applyNestedQueries(array $results, array $queries, array $relat */ public function findOne(string $collection, array $queries = []): bool|Document { - $results = $this->silent(fn () => $this->find($collection, \array_merge([Query::limit(1)], $queries))); + $results = $this->silent(fn () => $this->find($collection, \array_merge([ + Query::limit(1) + ], $queries))); + $found = \reset($results); $this->trigger(self::EVENT_DOCUMENT_FIND, $found); @@ -4319,6 +4227,14 @@ public function count(string $collection, array $queries = [], ?int $max = null) throw new DatabaseException("Collection not found"); } + $attributes = $collection->getAttribute('attributes', []); + $indexes = $collection->getAttribute('indexes', []); + + $validator = new DocumentsValidator($attributes, $indexes); + if (!$validator->isValid($queries)) { + throw new QueryException($validator->getDescription()); + } + $authorization = new Authorization(self::PERMISSION_READ); if ($authorization->isValid($collection->getRead())) { $skipAuth = true; @@ -4356,7 +4272,16 @@ public function sum(string $collection, string $attribute, array $queries = [], throw new DatabaseException("Collection not found"); } + $attributes = $collection->getAttribute('attributes', []); + $indexes = $collection->getAttribute('indexes', []); + + $validator = new DocumentsValidator($attributes, $indexes); + if (!$validator->isValid($queries)) { + throw new QueryException($validator->getDescription()); + } + $queries = self::convertQueries($collection, $queries); + $sum = $this->adapter->sum($collection->getId(), $attribute, $queries, $max); $this->trigger(self::EVENT_DOCUMENT_SUM, $sum); diff --git a/src/Database/Exception/Query.php b/src/Database/Exception/Query.php new file mode 100644 index 000000000..58f699d12 --- /dev/null +++ b/src/Database/Exception/Query.php @@ -0,0 +1,9 @@ +