Add back validation for fulltext index on search queries
This commit is contained in:
parent
72d090f196
commit
ed54d9861f
5 changed files with 46 additions and 79 deletions
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 [];
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue