diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index fd53bf603..72c1e7562 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -37,10 +37,14 @@ class Queries extends Validator * @param Document[] $indexes * @param bool $strict */ - public function __construct($validator, $indexes, $strict = true) + public function __construct(QueryValidator $validator, array $indexes, bool $strict = true) { $this->validator = $validator; + foreach ($indexes as $index) { + $this->indexes[] = $index->getArrayCopy(['attributes', 'type']); + } + $this->indexes[] = [ 'type' => Database::INDEX_UNIQUE, 'attributes' => ['$id'] @@ -56,10 +60,6 @@ public function __construct($validator, $indexes, $strict = true) 'attributes' => ['$updatedAt'] ]; - foreach ($indexes as $index) { - $this->indexes[] = $index->getArrayCopy(['attributes', 'type']); - } - $this->strict = $strict; } @@ -79,7 +79,7 @@ public function getDescription(): string * Is valid. * * Returns true if all $queries are valid as a set. - * @param mixed $value as array of Query objects + * @param Query[] $value as array of Query objects * @return bool */ public function isValid($value): bool @@ -93,7 +93,7 @@ public function isValid($value): bool foreach ($value as $query) { // [attribute => operator] - $queries[$query->getAttribute()] = $query->getOperator(); + $queries[$query->getAttribute()] = $query->getOperator(); if (!$this->validator->isValid($query)) { $this->message = 'Query not valid: ' . $this->validator->getDescription(); @@ -108,7 +108,7 @@ public function isValid($value): bool // look for strict match among indexes foreach ($this->indexes as $index) { if ($this->arrayMatch($index['attributes'], array_keys($queries))) { - $found = $index; + $found = $index; } } @@ -121,7 +121,7 @@ public function isValid($value): bool if (in_array(Query::TYPE_SEARCH, array_values($queries)) && $found['type'] !== Database::INDEX_FULLTEXT) { $this->message = 'Search operator requires fulltext index: ' . implode(",", array_keys($queries)); return false; - } + } } return true; @@ -170,13 +170,17 @@ public function isStrict(): bool * * @return bool */ - protected function arrayMatch($indexes, $queries): bool + protected function arrayMatch(array $indexes, array $queries): bool { // Check the count of indexes first for performance - if (count($indexes) !== count($queries)) { + 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; diff --git a/src/Database/Validator/QueryValidator.php b/src/Database/Validator/QueryValidator.php index 53593c2fd..6741cf517 100644 --- a/src/Database/Validator/QueryValidator.php +++ b/src/Database/Validator/QueryValidator.php @@ -40,6 +40,10 @@ class QueryValidator extends Validator */ public function __construct(array $attributes) { + foreach ($attributes as $attribute) { + $this->schema[] = $attribute->getArrayCopy(); + } + $this->schema[] = [ 'key' => '$id', 'array' => false, @@ -60,10 +64,6 @@ public function __construct(array $attributes) 'type' => Database::VAR_INTEGER, 'size' => 0 ]; - - foreach ($attributes as $attribute) { - $this->schema[] = $attribute->getArrayCopy(); - } } /** diff --git a/tests/Database/Validator/QueriesTest.php b/tests/Database/Validator/QueriesTest.php index 1887893dd..2e53fd543 100644 --- a/tests/Database/Validator/QueriesTest.php +++ b/tests/Database/Validator/QueriesTest.php @@ -96,8 +96,9 @@ class QueriesTest extends TestCase public function setUp(): void { - // Query validator expects Document[] - $attributes = []; /** @var Document[] $attributes */ + /** @var Document[] $attributes */ + $attributes = []; + foreach ($this->collection['attributes'] as $attribute) { $attributes[] = new Document($attribute); } @@ -138,6 +139,7 @@ public function setUp(): void 'DESC' ], ]); + $index3 = new Document([ '$id' => 'testindex3', 'type' => 'fulltext', @@ -146,6 +148,7 @@ public function setUp(): void ], 'orders' => [] ]); + $index4 = new Document([ '$id' => 'testindex4', 'type' => 'key', @@ -164,23 +167,22 @@ public function tearDown(): void public function testQueries() { - // test for SUCCESS + // Test for SUCCESS $validator = new Queries($this->queryValidator, $this->collection['indexes']); - $this->assertEquals(true, $validator->isValid($this->queries)); + $this->assertTrue($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('price.greaterEqual(5.50)'); + $this->assertTrue($validator->isValid($this->queries)); + // Test for FAILURE $this->queries[] = Query::parse('rating.greater(4)'); - $this->assertEquals(false, $validator->isValid($this->queries)); + $this->assertFalse($validator->isValid($this->queries)); $this->assertEquals("Index not found: title,description,price,rating", $validator->getDescription()); - // test for queued index + // Test for queued index $query1 = Query::parse('price.lesserEqual(6.50)'); $query2 = Query::parse('title.notEqual("Iron Man", "Ant Man")'); @@ -188,22 +190,76 @@ public function testQueries() $this->assertEquals(false, $validator->isValid($this->queries)); $this->assertEquals("Index not found: price,title", $validator->getDescription()); - // test fulltext - + // Test fulltext $query3 = Query::parse('description.search("iron")'); $this->queries = [$query3]; - $this->assertEquals(false, $validator->isValid($this->queries)); + $this->assertFalse($validator->isValid($this->queries)); $this->assertEquals("Search operator requires fulltext index: description", $validator->getDescription()); } + public function testLooseOrderQueries() + { + $validator = new Queries($this->queryValidator, [new Document([ + '$id' => 'testindex5', + 'type' => 'key', + 'attributes' => [ + 'title', + 'price', + 'rating' + ], + 'orders' => [] + ])], true); + + // Test for SUCCESS + $this->assertTrue($validator->isValid([ + Query::parse('price.lesserEqual(6.50)'), + Query::parse('title.lesserEqual("string")'), + Query::parse('rating.lesserEqual(2002)') + ])); + + $this->assertTrue($validator->isValid([ + Query::parse('price.lesserEqual(6.50)'), + Query::parse('title.lesserEqual("string")'), + Query::parse('rating.lesserEqual(2002)') + ])); + + $this->assertTrue($validator->isValid([ + Query::parse('price.lesserEqual(6.50)'), + Query::parse('rating.lesserEqual(2002)'), + Query::parse('title.lesserEqual("string")') + ])); + + $this->assertTrue($validator->isValid([ + Query::parse('title.lesserEqual("string")'), + Query::parse('price.lesserEqual(6.50)'), + Query::parse('rating.lesserEqual(2002)') + ])); + + $this->assertTrue($validator->isValid([ + Query::parse('title.lesserEqual("string")'), + Query::parse('rating.lesserEqual(2002)'), + Query::parse('price.lesserEqual(6.50)') + ])); + + $this->assertTrue($validator->isValid([ + Query::parse('rating.lesserEqual(2002)'), + Query::parse('title.lesserEqual("string")'), + Query::parse('price.lesserEqual(6.50)') + ])); + + $this->assertTrue($validator->isValid([ + Query::parse('rating.lesserEqual(2002)'), + Query::parse('price.lesserEqual(6.50)'), + Query::parse('title.lesserEqual("string")') + ])); + } + public function testIsStrict() { $validator = new Queries($this->queryValidator, $this->collection['indexes']); - - $this->assertEquals(true, $validator->isStrict()); + $this->assertTrue($validator->isStrict()); $validator = new Queries($this->queryValidator, $this->collection['indexes'], false); - - $this->assertEquals(false, $validator->isStrict()); + $this->assertFalse($validator->isStrict()); } }