1
0
Fork 0
mirror of synced 2024-10-03 19:53:33 +13:00

Add back validation for fulltext index on search queries

This commit is contained in:
Jake Barnby 2023-03-30 19:57:56 +13:00
parent 72d090f196
commit ed54d9861f
No known key found for this signature in database
GPG key ID: C437A8CC85B96E9C
5 changed files with 46 additions and 79 deletions

View file

@ -2817,7 +2817,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
}
// Validate queries
$queriesValidator = new Documents($collection->getAttribute('attributes'));
$queriesValidator = new Documents($collection->getAttribute('attributes'), $collection->getAttribute('indexes'));
$validQueries = $queriesValidator->isValid($queries);
if (!$validQueries) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $queriesValidator->getDescription());
@ -2848,6 +2848,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
unset($filterQueries[$key]);
}
}
$documents = Authorization::skip(fn () => $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries));
$documentSecurity = $collection->getAttribute('documentSecurity', false);

View file

@ -10,26 +10,26 @@ use Utopia\Database\Query;
class IndexedQueries extends Queries
{
/**
* @var Document[]
* @var array<Document>
*/
protected $attributes = [];
protected array $attributes = [];
/**
* @var Document[]
* @var array<Document>
*/
protected $indexes = [];
protected array $indexes = [];
/**
* Expression constructor
*
* This Queries Validator filters indexes for only available indexes
*
* @param Document[] $attributes
* @param Document[] $indexes
* @param array<Document> $attributes
* @param array<Document> $indexes
* @param Base ...$validators
* @throws \Exception
*/
public function __construct($attributes = [], $indexes = [], Base ...$validators)
public function __construct(array $attributes = [], array $indexes = [], Base ...$validators)
{
$this->attributes = $attributes;
@ -55,33 +55,6 @@ class IndexedQueries extends Queries
parent::__construct(...$validators);
}
/**
* Check if indexed array $indexes matches $queries
*
* @param array $indexes
* @param array $queries
*
* @return bool
*/
protected function arrayMatch(array $indexes, array $queries): bool
{
// Check the count of indexes first for performance
if (count($queries) !== count($indexes)) {
return false;
}
// Sort them for comparison, the order is not important here anymore.
sort($indexes, SORT_STRING);
sort($queries, SORT_STRING);
// Only matching arrays will have equal diffs in both directions
if (array_diff_assoc($indexes, $queries) !== array_diff_assoc($queries, $indexes)) {
return false;
}
return true;
}
/**
* Is valid.
*
@ -111,41 +84,26 @@ class IndexedQueries extends Queries
}
$grouped = Query::groupByType($queries);
/** @var Query[] */ $filters = $grouped['filters'];
/** @var string[] */ $orderAttributes = $grouped['orderAttributes'];
$filters = $grouped['filters'];
// Check filter queries for exact index match
if (count($filters) > 0) {
$filtersByAttribute = [];
foreach ($filters as $filter) {
$filtersByAttribute[$filter->getAttribute()] = $filter->getMethod();
}
foreach ($filters as $filter) {
if ($filter->getMethod() === Query::TYPE_SEARCH) {
$matched = false;
$found = null;
foreach ($this->indexes as $index) {
if (
$index->getAttribute('type') === Database::INDEX_FULLTEXT
&& $index->getAttribute('attributes') === [$filter->getAttribute()]
) {
$matched = true;
}
}
foreach ($this->indexes as $index) {
if ($this->arrayMatch($index->getAttribute('attributes'), array_keys($filtersByAttribute))) {
$found = $index;
if (!$matched) {
$this->message = "Searching by attribute \"{$filter->getAttribute()}\" requires a fulltext index.";
return false;
}
}
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;

View file

@ -11,12 +11,12 @@ class Queries extends Validator
/**
* @var string
*/
protected $message = 'Invalid queries';
protected string $message = 'Invalid queries';
/**
* @var Base[]
* @var array<Base>
*/
protected $validators;
protected array $validators;
/**
* Queries constructor

View file

@ -2,7 +2,7 @@
namespace Appwrite\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\IndexedQueries;
use Appwrite\Utopia\Database\Validator\Query\Cursor;
use Appwrite\Utopia\Database\Validator\Query\Filter;
use Appwrite\Utopia\Database\Validator\Query\Limit;
@ -12,7 +12,7 @@ use Appwrite\Utopia\Database\Validator\Query\Select;
use Utopia\Database\Database;
use Utopia\Database\Document;
class Documents extends Queries
class Documents extends IndexedQueries
{
/**
* Expression constructor
@ -20,7 +20,7 @@ class Documents extends Queries
* @param Document[] $attributes
* @throws \Exception
*/
public function __construct(array $attributes)
public function __construct(array $attributes, array $indexes)
{
$attributes[] = new Document([
'key' => '$id',
@ -47,6 +47,6 @@ class Documents extends Queries
new Select($attributes),
];
parent::__construct(...$validators);
parent::__construct($attributes, $indexes, ...$validators);
}
}

View file

@ -1577,6 +1577,18 @@ trait DatabasesBase
]);
$this->assertEquals(200, $documents['headers']['status-code']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['greaterThan("birthDay", "1960-01-01 10:10:10+02:30")'],
]);
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals('1975-06-12T12:12:55.000+00:00', $documents['body']['documents'][0]['birthDay']);
$this->assertEquals('1975-06-12T18:12:55.000+00:00', $documents['body']['documents'][1]['birthDay']);
$this->assertCount(2, $documents['body']['documents']);
/**
* Test for Failure
*/
@ -1610,18 +1622,14 @@ trait DatabasesBase
$this->assertEquals(400, $documents['headers']['status-code']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => ['greaterThan("birthDay", "1960-01-01 10:10:10+02:30")'],
'queries' => ['search("actors", "Tom")'],
]);
$this->assertEquals($documents['headers']['status-code'], 200);
$this->assertEquals('1975-06-12T12:12:55.000+00:00', $documents['body']['documents'][0]['birthDay']);
$this->assertEquals('1975-06-12T18:12:55.000+00:00', $documents['body']['documents'][1]['birthDay']);
$this->assertCount(2, $documents['body']['documents']);
$this->assertEquals(400, $documents['headers']['status-code']);
$this->assertEquals('Searching by attribute "actors" requires a fulltext index.', $documents['body']['message']);
return [];
}