Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix throwing global exception instead of database exception #310

Merged
merged 5 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
},
"require": {
"ext-pdo": "*",
"ext-mbstring": "*",
"php": ">=8.0",
"utopia-php/framework": "0.*.*",
"utopia-php/cache": "0.8.*",
Expand Down
2 changes: 1 addition & 1 deletion src/Database/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
150 changes: 38 additions & 112 deletions src/Database/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -4177,112 +4188,6 @@ public function find(string $collection, array $queries = [], ?int $timeout = nu
return $results;
}

/**
* @param array<Document> $results
* @param array<Query> $queries
* @param array<Document> $relationships
* @return array<Document>
*/
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<Query> $queries
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -4327,6 +4243,7 @@ public function count(string $collection, array $queries = [], ?int $max = null)
$queries = Query::groupByType($queries)['filters'];
$queries = self::convertQueries($collection, $queries);


$getCount = fn () => $this->adapter->count($collection->getId(), $queries, $max);
$count = $skipAuth ?? false ? Authorization::skip($getCount) : $getCount();

Expand Down Expand Up @@ -4356,7 +4273,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);
Expand Down
9 changes: 9 additions & 0 deletions src/Database/Exception/Query.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Utopia\Database\Exception;

use Utopia\Database\Exception;

class Query extends Exception
{
}
8 changes: 4 additions & 4 deletions src/Database/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Utopia\Database;

use Utopia\Database\Exception as DatabaseException;
use Utopia\Database\Exception\Query as QueryException;

class Query
{
Expand Down Expand Up @@ -200,7 +200,7 @@ public static function parse(string $filter): self
$paramsStart = mb_strpos($filter, static::CHAR_PARENTHESES_START);

if ($paramsStart === false) {
throw new DatabaseException("Invalid query");
throw new QueryException("Invalid query");
}

$method = mb_substr($filter, 0, $paramsStart);
Expand All @@ -211,7 +211,7 @@ public static function parse(string $filter): self

// Check for deprecated query syntax
if (\str_contains($method, '.')) {
throw new DatabaseException("Invalid query method");
throw new QueryException("Invalid query method");
}

$currentParam = ""; // We build param here before pushing when it's ended
Expand Down Expand Up @@ -822,7 +822,7 @@ public static function parseQueries(array $queries): array
try {
$parsed[] = Query::parse($query);
} catch (\Throwable $th) {
throw new DatabaseException("Invalid query: ${query}", previous: $th);
throw new QueryException("Invalid query: ${query}", previous: $th);
}
}

Expand Down