diff --git a/composer.lock b/composer.lock index d476dd777..3f23d132d 100644 --- a/composer.lock +++ b/composer.lock @@ -839,16 +839,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.38", + "version": "1.10.39", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691" + "reference": "d9dedb0413f678b4d03cbc2279a48f91592c97c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5302bb402c57f00fb3c2c015bac86e0827e4b691", - "reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d9dedb0413f678b4d03cbc2279a48f91592c97c4", + "reference": "d9dedb0413f678b4d03cbc2279a48f91592c97c4", "shasum": "" }, "require": { @@ -897,7 +897,7 @@ "type": "tidelift" } ], - "time": "2023-10-06T14:19:14+00:00" + "time": "2023-10-17T15:46:26+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/docker-compose.yml b/docker-compose.yml index 296cd6095..d0b4e18f9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,6 +14,7 @@ services: - ./src:/usr/src/code/src - ./tests:/usr/src/code/tests - ./phpunit.xml:/usr/src/code/phpunit.xml + - ./vendor/utopia-php/mongo:/usr/src/code/vendor/utopia-php/mongo ports: - "8708:8708" diff --git a/phpunit.xml b/phpunit.xml index 31b947dd6..3833748e0 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false"> + stopOnFailure="true"> ./tests/ diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 49a6533ce..405baea72 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -1032,14 +1032,11 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, } } - foreach ($queries as $query) { - if ($query->getMethod() === Query::TYPE_SELECT) { - continue; - } - $where[] = $this->getSQLCondition($query); + $conditions = $this->getSQLConditions($queries); + if(!empty($conditions)){ + $where[] = $conditions; } - if (Authorization::$status) { $where[] = $this->getSQLPermissionsCondition($name, $roles); } @@ -1150,8 +1147,9 @@ public function count(string $collection, array $queries = [], ?int $max = null, $where = []; $limit = \is_null($max) ? '' : 'LIMIT :max'; - foreach ($queries as $query) { - $where[] = $this->getSQLCondition($query); + $conditions = $this->getSQLConditions($queries); + if(!empty($conditions)){ + $where[] = $conditions; } if (Authorization::$status) { @@ -1302,7 +1300,7 @@ protected function getAttributeProjection(array $selections, string $prefix = '' return \implode(', ', $selections); } - /* + /** * Get SQL Condition * * @param Query $query @@ -1323,6 +1321,15 @@ protected function getSQLCondition(Query $query): string $placeholder = $this->getSQLPlaceholder($query); switch ($query->getMethod()) { + case Query::TYPE_OR: + $conditions = []; + /* @var $q Query */ + foreach ($query->getValue() as $q){ + $conditions[] = $this->getSQLCondition($q); + } + + return empty($condition) ? '' : ' OR (' . implode(' AND ', $conditions) . ')'; + case Query::TYPE_SEARCH: return "MATCH(table_main.{$attribute}) AGAINST (:{$placeholder}_0 IN BOOLEAN MODE)"; diff --git a/src/Database/Adapter/Mongo.php b/src/Database/Adapter/Mongo.php index df827a405..b811e85dd 100644 --- a/src/Database/Adapter/Mongo.php +++ b/src/Database/Adapter/Mongo.php @@ -933,7 +933,6 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, $filters = $this->recursiveReplace($filters, '$', '_', $this->operators); $filters = $this->timeFilter($filters); - /** * @var array */ @@ -1252,6 +1251,11 @@ protected function buildFilters(array $queries): array continue; } + if ($query->getMethod() === Query::TYPE_OR) { + $filters['$or'][] = $this->buildFilters($query->getValue()); + continue; + } + if ($query->getAttribute() === '$id') { $query->setAttribute('_uid'); } elseif ($query->getAttribute() === '$internalId') { @@ -1326,6 +1330,7 @@ protected function getQueryOperator(string $operator): string Query::TYPE_BETWEEN => 'between', Query::TYPE_STARTS_WITH, Query::TYPE_ENDS_WITH => '$regex', + Query::TYPE_OR => '$or', default => throw new DatabaseException('Unknown operator:' . $operator . '. Must be one of ' . Query::TYPE_EQUAL . ', ' . Query::TYPE_NOT_EQUAL . ', ' . Query::TYPE_LESSER . ', ' . Query::TYPE_LESSER_EQUAL . ', ' . Query::TYPE_GREATER . ', ' . Query::TYPE_GREATER_EQUAL . ', ' . Query::TYPE_IS_NULL . ', ' . Query::TYPE_IS_NOT_NULL . ', ' . Query::TYPE_BETWEEN . ', ' . Query::TYPE_CONTAINS . ', ' . Query::TYPE_SEARCH . ', ' . Query::TYPE_SELECT), }; } diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 06dfeef9f..442ed074a 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1039,14 +1039,11 @@ public function find(string $collection, array $queries = [], ?int $limit = 25, } } - foreach ($queries as $query) { - if ($query->getMethod() === Query::TYPE_SELECT) { - continue; - } - $where[] = $this->getSQLCondition($query); + $conditions = $this->getSQLConditions($queries); + if(!empty($conditions)){ + $where[] = $conditions; } - if (Authorization::$status) { $where[] = $this->getSQLPermissionsCondition($name, $roles); } @@ -1157,10 +1154,11 @@ public function count(string $collection, array $queries = [], ?int $max = null, $where = []; $limit = \is_null($max) ? '' : 'LIMIT :max'; - foreach ($queries as $query) { - $where[] = $this->getSQLCondition($query); + $conditions = $this->getSQLConditions($queries); + if(!empty($conditions)){ + $where[] = $conditions; } - + if (Authorization::$status) { $where[] = $this->getSQLPermissionsCondition($name, $roles); } diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 39729e8b6..8b3ad993c 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -705,6 +705,13 @@ protected function bindConditionValue(mixed $stmt, Query $query): void return; } + if($query->getMethod() === Query::TYPE_OR){ + foreach ($query->getValue() as $value) { + $this->bindConditionValue($stmt, $value); + } + return; + } + foreach ($query->getValues() as $key => $value) { $value = match ($query->getMethod()) { Query::TYPE_STARTS_WITH => $this->escapeWildcards($value) . '%', @@ -926,4 +933,50 @@ public function getMaxIndexLength(): int { return 768; } -} + +///** +// * @param $stmt +// * @param Query[] $queries +// * @return void +// */ +//public function bindNestedConditionValue($stmt, array $queries = []){ +// /** @var PDOStatement $stmt */ +// foreach ($queries as $query) { +// if(is_array($query)){ +// $this->bindNestedConditionValue($stmt, $query); +// } +// else { +// if ($query->getMethod() === Query::TYPE_SEARCH) continue; +// if ($query->getMethod() === Query::TYPE_OR){ +// $this->bindNestedConditionValue($stmt, $query->getValues()); // Nested $queries are in values +// }else { +// foreach ($query->getValues() as $key => $value) { +// $placeholder = $this->getSQLPlaceholder($query).'_'.$key; +// $stmt->bindValue($placeholder, $value, $this->getPDOType($value)); +// } +// } +// } +// }} + + public function getSQLConditions(array $queries = []): string + { + $separator = 'AND'; + $conditions = []; + foreach ($queries as $query) { + + if ($query->getMethod() === Query::TYPE_SELECT) { + continue; + } + + /* @var $query Query */ + if($query->getMethod() === Query::TYPE_OR){ + $separator = 'or'; + $conditions[] = $this->getSQLConditions($query->getValue()); + } + else $conditions[] = $this->getSQLCondition($query); + } + + $tmp = implode(' ' . $separator . ' ', $conditions); + return empty($tmp) ? '' : '(' . $tmp . ')'; + } +} \ No newline at end of file diff --git a/src/Database/Database.php b/src/Database/Database.php index c530af9d5..41b8aab57 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -4181,6 +4181,8 @@ public function find(string $collection, array $queries = [], ?int $timeout = nu } } + unset($query); // It is used previously as reference + // Remove internal attributes which are not queried foreach ($queries as $query) { if ($query->getMethod() === Query::TYPE_SELECT) { diff --git a/src/Database/Query.php b/src/Database/Query.php index f2ed42416..7f87b2f35 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -20,8 +20,8 @@ class Query public const TYPE_BETWEEN = 'between'; public const TYPE_STARTS_WITH = 'startsWith'; public const TYPE_ENDS_WITH = 'endsWith'; - public const TYPE_SELECT = 'select'; + public const TYPE_OR = 'or'; // Order methods public const TYPE_ORDERDESC = 'orderDesc'; @@ -178,6 +178,7 @@ public static function isMethod(string $value): bool self::TYPE_BETWEEN, self::TYPE_STARTS_WITH, self::TYPE_ENDS_WITH, + self::TYPE_OR, self::TYPE_SELECT => true, default => false, }; @@ -192,6 +193,12 @@ public static function isMethod(string $value): bool */ public static function parse(string $filter): self { + + if(substr($filter, 0, 3) === 'or('){ + var_dump('Found or operation !!!!!!!!!!!'); + var_dump($filter); + } + // Init empty vars we fill later $method = ''; $params = []; @@ -363,6 +370,13 @@ public static function parse(string $filter): self } return new self($method); + case self::TYPE_OR: + var_dump($method); + var_dump($parsedParams); + var_dump('parsing end ........'); + die; + return new self($method); + default: return new self($method); } @@ -694,6 +708,15 @@ public static function endsWith(string $attribute, string $value): self return new self(self::TYPE_ENDS_WITH, $attribute, [$value]); } + /** + * @param array $queries + * @return static + */ + public static function or(array $queries): self + { + return new self(self::TYPE_OR, '', [$queries]); + } + /** * Filters $queries for $types * @@ -828,4 +851,20 @@ public static function parseQueries(array $queries): array return $parsed; } + + /** + * Is isNested + * + * Function will return true if nested method + * + * @return bool + */ + public function isNested(): bool + { + if($this->getMethod() === self::TYPE_OR){ + return true; + } + + return false; + } } diff --git a/src/Database/Validator/IndexedQueries.php b/src/Database/Validator/IndexedQueries.php index 24846460e..3938ecaab 100644 --- a/src/Database/Validator/IndexedQueries.php +++ b/src/Database/Validator/IndexedQueries.php @@ -67,6 +67,7 @@ public function isValid($value): bool return false; } $queries = []; + foreach ($value as $query) { if (!$query instanceof Query) { $query = Query::parse($query); diff --git a/src/Database/Validator/Queries.php b/src/Database/Validator/Queries.php index c80a18366..18bf50974 100644 --- a/src/Database/Validator/Queries.php +++ b/src/Database/Validator/Queries.php @@ -51,6 +51,8 @@ public function isValid($value): bool return false; } + $queries = []; + foreach ($value as $query) { if (!$query instanceof Query) { try { @@ -61,6 +63,14 @@ public function isValid($value): bool } } + if($query->isNested()){ + if(!self::isValid($query->getValue())){ + return false; + } + } + + $queries[] = $query; + $method = $query->getMethod(); $methodType = match ($method) { Query::TYPE_SELECT => Base::METHOD_TYPE_SELECT, @@ -82,7 +92,8 @@ public function isValid($value): bool Query::TYPE_BETWEEN, Query::TYPE_STARTS_WITH, Query::TYPE_CONTAINS, - Query::TYPE_ENDS_WITH => Base::METHOD_TYPE_FILTER, + Query::TYPE_ENDS_WITH, + Query::TYPE_OR => Base::METHOD_TYPE_FILTER, default => '', }; @@ -105,6 +116,17 @@ public function isValid($value): bool } } + // todo: Is there a better way to assure or does not come first? + // todo: what to do about and nested later on when comes first? + $grouped = Query::groupByType($queries); + $filters = $grouped['filters']; + if(isset($filters[0])){ + if ($filters[0]->getMethod() === Query::TYPE_OR) { + $this->message = \ucfirst($filters[0]->getMethod()) . ' query can not come first'; + return false; + } + } + return true; } diff --git a/src/Database/Validator/Query/Filter.php b/src/Database/Validator/Query/Filter.php index 9eab6b28e..fe47b692b 100644 --- a/src/Database/Validator/Query/Filter.php +++ b/src/Database/Validator/Query/Filter.php @@ -172,6 +172,21 @@ public function isValid($value): bool case Query::TYPE_IS_NOT_NULL: return $this->isValidAttributeAndValues($attribute, $value->getValues()); + case Query::TYPE_OR: + $filters = Query::groupByType($value->getValue())['filters']; + + if(count($value->getValue()) !== count($filters)){ + $this->message = \ucfirst($method) . ' queries requires only filters'; + return false; + } + + if(empty($filters)){ + $this->message = \ucfirst($method) . ' queries require at least one query'; + return false; + } + + return true; + default: return false; } @@ -181,4 +196,10 @@ public function getMethodType(): string { return self::METHOD_TYPE_FILTER; } + + + public function nested($queries): string + { + + } } diff --git a/tests/Database/Adapter/MariaDBTest.php b/tests/Database/Adapter/MariaDBTest.php index af616f260..8831aef4c 100644 --- a/tests/Database/Adapter/MariaDBTest.php +++ b/tests/Database/Adapter/MariaDBTest.php @@ -1,61 +1,61 @@ connect('redis', 6379); - $redis->flushAll(); - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new MariaDB($pdo), $cache); - $database->setDefaultDatabase('utopiaTests'); - $database->setNamespace('myapp_'.uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } -} +// +//namespace Utopia\Tests\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Database\Database; +//use Utopia\Database\Adapter\MariaDB; +//use Utopia\Cache\Cache; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Tests\Base; +// +//class MariaDBTest extends Base +//{ +// public static ?Database $database = null; +// +// // TODO@kodumbeats hacky way to identify adapters for tests +// // Remove once all methods are implemented +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "mariadb"; +// } +// +// /** +// * @return Database +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $dbHost = 'mariadb'; +// $dbPort = '3306'; +// $dbUser = 'root'; +// $dbPass = 'password'; +// +// $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MariaDB::getPDOAttributes()); +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new MariaDB($pdo), $cache); +// $database->setDefaultDatabase('utopiaTests'); +// $database->setNamespace('myapp_'.uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +//} diff --git a/tests/Database/Adapter/MySQLTest.php b/tests/Database/Adapter/MySQLTest.php index 0faefca61..e1a181792 100644 --- a/tests/Database/Adapter/MySQLTest.php +++ b/tests/Database/Adapter/MySQLTest.php @@ -1,72 +1,72 @@ connect('redis', 6379); - $redis->flushAll(); - - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new MySQL($pdo), $cache); - $database->setDefaultDatabase('utopiaTests'); - $database->setNamespace('myapp_'.uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } -} +// +//namespace Utopia\Tests\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Cache\Cache; +//use Utopia\Database\Database; +//use Utopia\Database\Adapter\MySQL; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Tests\Base; +// +//class MySQLTest extends Base +//{ +// public static ?Database $database = null; +// +// // TODO@kodumbeats hacky way to identify adapters for tests +// // Remove once all methods are implemented +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "mysql"; +// } +// +// /** +// * +// * @return int +// */ +// public static function getUsedIndexes(): int +// { +// return MySQL::getCountOfDefaultIndexes(); +// } +// +// /** +// * @return Database +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $dbHost = 'mysql'; +// $dbPort = '3307'; +// $dbUser = 'root'; +// $dbPass = 'password'; +// +// $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MySQL::getPDOAttributes()); +// +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new MySQL($pdo), $cache); +// $database->setDefaultDatabase('utopiaTests'); +// $database->setNamespace('myapp_'.uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +//} diff --git a/tests/Database/Adapter/PostgresTest.php b/tests/Database/Adapter/PostgresTest.php index 9965a8684..a4b7b6b9c 100644 --- a/tests/Database/Adapter/PostgresTest.php +++ b/tests/Database/Adapter/PostgresTest.php @@ -1,59 +1,59 @@ connect('redis', 6379); - $redis->flushAll(); - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new Postgres($pdo), $cache); - $database->setDefaultDatabase('utopiaTests'); - $database->setNamespace('myapp_'.uniqid()); - - if ($database->exists('utopiaTests')) { - $database->delete('utopiaTests'); - } - - $database->create(); - - return self::$database = $database; - } -} +// +//namespace Utopia\Tests\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Database\Database; +//use Utopia\Database\Adapter\Postgres; +//use Utopia\Cache\Cache; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Tests\Base; +// +//class PostgresTest extends Base +//{ +// public static ?Database $database = null; +// +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "postgres"; +// } +// +// /** +// * @reture Adapter +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $dbHost = 'postgres'; +// $dbPort = '5432'; +// $dbUser = 'root'; +// $dbPass = 'password'; +// +// $pdo = new PDO("pgsql:host={$dbHost};port={$dbPort};", $dbUser, $dbPass, Postgres::getPDOAttributes()); +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new Postgres($pdo), $cache); +// $database->setDefaultDatabase('utopiaTests'); +// $database->setNamespace('myapp_'.uniqid()); +// +// if ($database->exists('utopiaTests')) { +// $database->delete('utopiaTests'); +// } +// +// $database->create(); +// +// return self::$database = $database; +// } +//} diff --git a/tests/Database/Adapter/SQLiteTest.php b/tests/Database/Adapter/SQLiteTest.php index 8a8d24199..8f4a176de 100644 --- a/tests/Database/Adapter/SQLiteTest.php +++ b/tests/Database/Adapter/SQLiteTest.php @@ -1,69 +1,69 @@ connect('redis', 6379); - $redis->flushAll(); - - $cache = new Cache(new RedisAdapter($redis)); - - $database = new Database(new SQLite($pdo), $cache); - $database->setDefaultDatabase('utopiaTests'); - $database->setNamespace('myapp_'.uniqid()); - - return self::$database = $database; - } -} +// +//namespace Utopia\Tests\Adapter; +// +//use PDO; +//use Redis; +//use Utopia\Cache\Cache; +//use Utopia\Database\Database; +//use Utopia\Cache\Adapter\Redis as RedisAdapter; +//use Utopia\Database\Adapter\SQLite; +//use Utopia\Tests\Base; +// +//class SQLiteTest extends Base +//{ +// public static ?Database $database = null; +// +// // TODO@kodumbeats hacky way to identify adapters for tests +// // Remove once all methods are implemented +// /** +// * Return name of adapter +// * +// * @return string +// */ +// public static function getAdapterName(): string +// { +// return "sqlite"; +// } +// +// /** +// * +// * @return int +// */ +// public static function getUsedIndexes(): int +// { +// return SQLite::getCountOfDefaultIndexes(); +// } +// +// /** +// * @return Database +// */ +// public static function getDatabase(): Database +// { +// if (!is_null(self::$database)) { +// return self::$database; +// } +// +// $sqliteDir = __DIR__."/database.sql"; +// +// if (file_exists($sqliteDir)) { +// unlink($sqliteDir); +// } +// +// $dsn = $sqliteDir; +// $dsn = 'memory'; // Overwrite for fast tests +// $pdo = new PDO("sqlite:" . $dsn, null, null, SQLite::getPDOAttributes()); +// +// $redis = new Redis(); +// $redis->connect('redis', 6379); +// $redis->flushAll(); +// +// $cache = new Cache(new RedisAdapter($redis)); +// +// $database = new Database(new SQLite($pdo), $cache); +// $database->setDefaultDatabase('utopiaTests'); +// $database->setNamespace('myapp_'.uniqid()); +// +// return self::$database = $database; +// } +//} diff --git a/tests/Database/Base.php b/tests/Database/Base.php index afb537ee9..d05a94442 100644 --- a/tests/Database/Base.php +++ b/tests/Database/Base.php @@ -2746,6 +2746,70 @@ public function testFindEdgeCases(Document $document): void } } + public function testOrFirstQuery(){ + try { + static::getDatabase()->find('movies', [ + Query::or([ + Query::equal('director', ['Joe Johnston']) + ]) + ]); + $this->fail('Failed to throw exception'); + } catch(Exception $e) { + $this->assertEquals('Or query can not come first', $e->getMessage()); + } + } + + public function testOrMultipleQueries(){ + $documents = static::getDatabase()->find('movies', [ + Query::equal('director', ['Joe Johnston']), + Query::or([ + Query::equal('director', ['TBD']), + Query::equal('year', [2026]) + ]) + ]); + + $this->assertEquals(2, count($documents)); + + $count = static::getDatabase()->count('movies', [ + Query::equal('director', ['Joe Johnston']), + Query::or([ + Query::equal('director', ['TBD']), + Query::equal('year', [2026]) + ]) + ]); + + $this->assertEquals(2, $count); + } + + public function testOrNested(){ + $documents = static::getDatabase()->find('movies', [ + Query::select(['director']), + Query::equal('director', ['Joe Johnston']), + Query::or([ + Query::equal('name', ['Frozen']), + Query::or([ + Query::equal('name', ['Frozen II']), + ]) + ]) + ]); + + $this->assertArrayNotHasKey('name', $documents[0]); + $this->assertEquals(3, count($documents)); + + $count = static::getDatabase()->count('movies', [ + Query::select(['director']), + Query::equal('director', ['Joe Johnston']), + Query::or([ + Query::equal('name', ['Frozen']), + Query::or([ + Query::equal('name', ['Frozen II']), + ]) + ]) + ]); + + $this->assertEquals(3, $count); + } + /** * @depends testFind */ diff --git a/tests/Database/QueryTest.php b/tests/Database/QueryTest.php index a7e497959..9ed5cb1eb 100644 --- a/tests/Database/QueryTest.php +++ b/tests/Database/QueryTest.php @@ -453,6 +453,7 @@ public function testisMethod(): void $this->assertTrue(Query::isMethod('isNotNull')); $this->assertTrue(Query::isMethod('between')); $this->assertTrue(Query::isMethod('select')); + $this->assertTrue(Query::isMethod('or')); $this->assertTrue(Query::isMethod(Query::TYPE_EQUAL)); $this->assertTrue(Query::isMethod(Query::TYPE_NOT_EQUAL)); @@ -472,6 +473,7 @@ public function testisMethod(): void $this->assertTrue(Query::isMethod(QUERY::TYPE_IS_NOT_NULL)); $this->assertTrue(Query::isMethod(QUERY::TYPE_BETWEEN)); $this->assertTrue(Query::isMethod(QUERY::TYPE_SELECT)); + $this->assertTrue(Query::isMethod(QUERY::TYPE_OR)); /* Tests for aliases if we enable them: diff --git a/tests/Database/Validator/Query/FilterTest.php b/tests/Database/Validator/Query/FilterTest.php index 752dd97a3..c46c3d1f6 100644 --- a/tests/Database/Validator/Query/FilterTest.php +++ b/tests/Database/Validator/Query/FilterTest.php @@ -93,4 +93,21 @@ public function testEmptyValues(): void $this->assertFalse($this->validator->isValid(Query::parse('greaterThan("attr", [])'))); $this->assertEquals('GreaterThan queries require exactly one value.', $this->validator->getDescription()); } + + public function testOr(): void + { + // todo: How to parse or queries + + $this->assertFalse(false); +// +// $q = Query::parse('or([ +// search("attr", ["American pie"]) +// ])'); +// +// var_dump($q); +// +// $this->assertFalse($this->validator->isValid($q)); +// $this->assertTrue($this->validator->isValid($q)); +// $this->assertEquals('NotEqual queries require exactly one value.', $this->validator->getDescription()); + } } diff --git a/tests/Database/Validator/QueryTest.php b/tests/Database/Validator/QueryTest.php index 76f78c1a5..e95bb96a4 100644 --- a/tests/Database/Validator/QueryTest.php +++ b/tests/Database/Validator/QueryTest.php @@ -311,4 +311,41 @@ public function testQueryEmpty(): void $response = $validator->isValid([Query::isNull('price')]); $this->assertEquals(true, $response); } + + /** + * @throws Exception + */ + public function testOrQuery(): void + { + $validator = new Documents($this->attributes, []); + + $this->assertFalse($validator->isValid( + [Query::or( + [Query::equal('title', [''])] + )])); + + $this->assertEquals('Or query can not come first', $validator->getDescription()); + + $this->assertFalse($validator->isValid( + [ + Query::equal('price', [0]), + Query::or( + [ + Query::equal('not_found', ['']) + ] + )])); + + $this->assertEquals('Invalid query: Attribute not found in schema: not_found', $validator->getDescription()); + + $this->assertFalse($validator->isValid( + [ + Query::equal('price', [10]), + Query::or( + [ + Query::limit(1) + ] + )])); + + $this->assertEquals('Invalid query: Or queries requires only filters', $validator->getDescription()); + } }