Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
177 changes: 177 additions & 0 deletions src/Database/Validator/Queries.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<?php

namespace Utopia\Database\Validator;

use Utopia\Validator;
use Utopia\Database\Validator\QueryValidator;
use Utopia\Database\Query;

class Queries extends Validator
{
/**
* @var string
*/
protected $message = 'Invalid queries';

/**
* @var QueryValidator
*/
protected $validator;

/**
* @var array
*/
protected $indexes = [];

/**
* @var array
*/
protected $indexesInQueue = [];

/**
* @var bool
*/
protected $strict;

/**
* Queries constructor
*
* @param QueryValidator $validator
* @param array $indexes
* @param array $indexesInQueue
* @param bool $strict
*/
public function __construct($validator, $indexes, $indexesInQueue, $strict = true)
{
$this->validator = $validator;
$this->indexes = $indexes;
$this->indexesInQueue = $indexesInQueue;
$this->strict = $strict;
}

/**
* Get Description.
*
* Returns validator description
*
* @return string
*/
public function getDescription()
{
return $this->message;
}

/**
* Is valid.
*
* Returns true if all $queries are valid as a set.
* @param mixed $value as array of Query objects
* @return bool
*/
public function isValid($value)
{
/**
* Array of attributes from Query->getAttribute()
*
* @var string[]
*/
$queries = [];

foreach ($value as $query) {
$queries[] = $query->getAttribute();

if (!$this->validator->isValid($query)) {
$this->message = 'Query not valid: ' . $this->validator->getDescription();
return false;
}
}

/**
* @var string
*/
$indexId = 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'], $queries)) {
$indexId = $index['$id'];
}
}

if (!$indexId) {
// check against the indexesInQueue
foreach ($this->indexesInQueue as $index) {
if ($this->arrayMatch($index['attributes'], $queries)) {
$this->message = 'Index still in creation queue: ' . implode(",", $queries);
return false;
}
}

$this->message = 'Index not found: ' . implode(",", $queries);
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($indexes, $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;
}
}
180 changes: 180 additions & 0 deletions tests/Database/Validator/QueriesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<?php

namespace Utopia\Tests\Validator;

use Utopia\Database\Validator\QueryValidator;
use PHPUnit\Framework\TestCase;
use Utopia\Database\Database;
use Utopia\Database\Query;
use Utopia\Database\Validator\Queries;

class QueriesTest extends TestCase
{
/**
* @var array
*/
protected $collection = [
'$id' => Database::COLLECTIONS,
'$collection' => Database::COLLECTIONS,
'name' => 'movies',
'attributes' => [
[
'$id' => 'title',
'type' => Database::VAR_STRING,
'size' => 256,
'required' => true,
'signed' => true,
'array' => false,
'filters' => [],
],
[
'$id' => 'description',
'type' => Database::VAR_STRING,
'size' => 1000000,
'required' => true,
'signed' => true,
'array' => false,
'filters' => [],
],
[
'$id' => 'rating',
'type' => Database::VAR_INTEGER,
'size' => 5,
'required' => true,
'signed' => true,
'array' => false,
'filters' => [],
],
[
'$id' => 'price',
'type' => Database::VAR_FLOAT,
'size' => 5,
'required' => true,
'signed' => true,
'array' => false,
'filters' => [],
],
[
'$id' => 'published',
'type' => Database::VAR_BOOLEAN,
'size' => 5,
'required' => true,
'signed' => true,
'array' => false,
'filters' => [],
],
[
'$id' => 'tags',
'type' => Database::VAR_STRING,
'size' => 55,
'required' => true,
'signed' => true,
'array' => true,
'filters' => [],
],
],
'indexes' => [
[
'$id' => 'testindex',
'type' => 'text',
'attributes' => [
'title',
'description'
],
'orders' => [
'ASC',
'DESC'
],
],
[
'$id' => 'testindex2',
'type' => 'text',
'attributes' => [
'title',
'description',
'price'
],
'orders' => [
'ASC',
'DESC'
],
],
],
'indexesInQueue' => [
[
'$id' => 'testindex3',
'type' => 'text',
'attributes' => [
'price',
'title'
],
'orders' => [
'ASC',
'DESC'
]
]
]
];


/**
* @var Query[] $queries
*/
protected $queries = [];

/**
* @var QueryValidator
*/
protected $queryValidator = null;

public function setUp(): void
{
$this->queryValidator = new QueryValidator($this->collection['attributes']);

$query1 = Query::parse('title.notEqual("Iron Man", "Ant Man")');
$query2 = Query::parse('description.equal("Best movie ever")');

array_push($this->queries, $query1, $query2);
}

public function tearDown(): void
{
}

public function testQueries()
{
// test for SUCCESS
$validator = new Queries($this->queryValidator, $this->collection['indexes'], $this->collection['indexesInQueue']);

$this->assertEquals(true, $validator->isValid($this->queries));

$this->queries[] = Query::parse('price.lesserEqual(6.50)');
$this->assertEquals(true, $validator->isValid($this->queries));

// test for FAILURE
$this->queries[] = Query::parse('rating.greater(4)');

$this->assertEquals(false, $validator->isValid($this->queries));
$this->assertEquals("Index not found: title,description,price,rating", $validator->getDescription());

// test for queued index
$query1 = Query::parse('price.lesserEqual(6.50)');
$query2 = Query::parse('title.notEqual("Iron Man", "Ant Man")');

$this->queries = [$query1, $query2];
$this->assertEquals(false, $validator->isValid($this->queries));
$this->assertEquals("Index still in creation queue: price,title", $validator->getDescription());

}

public function testIsStrict()
{
$validator = new Queries($this->queryValidator, $this->collection['indexes'], $this->collection['indexesInQueue']);

$this->assertEquals(true, $validator->isStrict());

$validator = new Queries($this->queryValidator, $this->collection['indexes'], $this->collection['indexesInQueue'], false);

$this->assertEquals(false, $validator->isStrict());
}
}