orm: all/any operators

This commit is contained in:
Yuri Kuznetsov
2023-03-12 17:17:43 +02:00
parent 58ac0800f9
commit 8be1af0671
4 changed files with 226 additions and 5 deletions

View File

@@ -52,6 +52,16 @@ class Comparison implements WhereItem
private const OPERATOR_NOT_LIKE = '!*';
private const OPERATOR_IN_SUB_QUERY = '=s';
private const OPERATOR_NOT_IN_SUB_QUERY = '!=s';
private const OPERATOR_NOT_EQUAL_ANY = '!=any';
private const OPERATOR_GREATER_ANY = '>any';
private const OPERATOR_GREATER_OR_EQUAL_ANY = '>=any';
private const OPERATOR_LESS_ANY = '<any';
private const OPERATOR_LESS_OR_EQUAL_ANY = '<=any';
private const OPERATOR_EQUAL_ALL = '=all';
private const OPERATOR_GREATER_ALL = '>all';
private const OPERATOR_GREATER_OR_EQUAL_ALL = '>=all';
private const OPERATOR_LESS_ALL = '<all';
private const OPERATOR_LESS_OR_EQUAL_ALL = '<=all';
private string $rawKey;
private mixed $rawValue;
@@ -211,6 +221,126 @@ class Comparison implements WhereItem
return self::createInOrNotInArray(self::OPERATOR_NOT_EQUAL, $subject, $set);
}
/**
* Create '!= ANY' comparison.
*
* @param Expression $argument An expression.
* @param Select $subQuery A sub-query.
* @return self
*/
public static function notEqualAny(Expression $argument, Select $subQuery): self
{
return self::createComparison(self::OPERATOR_NOT_EQUAL_ANY, $argument, $subQuery);
}
/**
* Create '> ANY' comparison.
*
* @param Expression $argument An expression.
* @param Select $subQuery A sub-query.
* @return self
*/
public static function greaterAny(Expression $argument, Select $subQuery): self
{
return self::createComparison(self::OPERATOR_GREATER_ANY, $argument, $subQuery);
}
/**
* Create '< ANY' comparison.
*
* @param Expression $argument An expression.
* @param Select $subQuery A sub-query.
* @return self
*/
public static function lessAny(Expression $argument, Select $subQuery): self
{
return self::createComparison(self::OPERATOR_LESS_ANY, $argument, $subQuery);
}
/**
* Create '>= ANY' comparison.
*
* @param Expression $argument An expression.
* @param Select $subQuery A sub-query.
* @return self
*/
public static function greaterOrEqualAny(Expression $argument, Select $subQuery): self
{
return self::createComparison(self::OPERATOR_GREATER_OR_EQUAL_ANY, $argument, $subQuery);
}
/**
* Create '<= ANY' comparison.
*
* @param Expression $argument An expression.
* @param Select $subQuery A sub-query.
* @return self
*/
public static function lessOrEqualAny(Expression $argument, Select $subQuery): self
{
return self::createComparison(self::OPERATOR_LESS_OR_EQUAL_ANY, $argument, $subQuery);
}
/**
* Create '= ALL' comparison.
*
* @param Expression $argument An expression.
* @param Select $subQuery A sub-query.
* @return self
*/
public static function equalAll(Expression $argument, Select $subQuery): self
{
return self::createComparison(self::OPERATOR_EQUAL_ALL, $argument, $subQuery);
}
/**
* Create '> ALL' comparison.
*
* @param Expression $argument An expression.
* @param Select $subQuery A sub-query.
* @return self
*/
public static function greaterAll(Expression $argument, Select $subQuery): self
{
return self::createComparison(self::OPERATOR_GREATER_ALL, $argument, $subQuery);
}
/**
* Create '< ALL' comparison.
*
* @param Expression $argument An expression.
* @param Select $subQuery A sub-query.
* @return self
*/
public static function lessAll(Expression $argument, Select $subQuery): self
{
return self::createComparison(self::OPERATOR_LESS_ALL, $argument, $subQuery);
}
/**
* Create '>= ALL' comparison.
*
* @param Expression $argument An expression.
* @param Select $subQuery A sub-query.
* @return self
*/
public static function greaterOrEqualAll(Expression $argument, Select $subQuery): self
{
return self::createComparison(self::OPERATOR_GREATER_OR_EQUAL_ALL, $argument, $subQuery);
}
/**
* Create '<= ALL' comparison.
*
* @param Expression $argument An expression.
* @param Select $subQuery A sub-query.
* @return self
*/
public static function lessOrEqualAll(Expression $argument, Select $subQuery): self
{
return self::createComparison(self::OPERATOR_LESS_OR_EQUAL_ALL, $argument, $subQuery);
}
private static function createComparison(
string $operator,
Expression|string $argument1,

View File

@@ -108,6 +108,18 @@ abstract class BaseQueryComposer implements QueryComposer
'>',
'<',
'=',
'>=any',
'<=any',
'>any',
'<any',
'!=any',
'=any',
'>=all',
'<=all',
'>all',
'<all',
'!=all',
'=all',
];
/** @var array<string, string> */
@@ -117,6 +129,18 @@ abstract class BaseQueryComposer implements QueryComposer
'!=' => '<>',
'!*' => 'NOT LIKE',
'*' => 'LIKE',
'>=any' => '>= ANY',
'<=any' => '<= ANY',
'>any' => '> ANY',
'<any' => '< ANY',
'!=any' => '<> ANY',
'=any' => '= ANY',
'>=all' => '>= ALL',
'<=all' => '<= ALL',
'>all' => '> ALL',
'<all' => '< ALL',
'!=all' => '<> ALL',
'=all' => '= ALL',
];
/** @var array<string, string> */
@@ -2424,13 +2448,13 @@ abstract class BaseQueryComposer implements QueryComposer
}
if ($field === self::EXISTS_OPERATOR) {
if ($value instanceof Select) {
$subQueryPart = $this->composeSelect($value);
}
else {
if (!$value instanceof Select) {
throw new RuntimeException("Bad EXISTS usage in where-clause.");
}
$subQueryPart = $this->composeSelect($value);
return "EXISTS ({$subQueryPart})";
}
@@ -2482,7 +2506,6 @@ abstract class BaseQueryComposer implements QueryComposer
return $this->quote(false);
}
// @todo Operators (<s, >s, <=s, =>s) producing 'operator ANY (sub-query)'.
if ($operatorOrm === '=s' || $operatorOrm === '!=s') {
if ($value instanceof Select) {
$subSql = $this->composeSelect($value);
@@ -2525,6 +2548,10 @@ abstract class BaseQueryComposer implements QueryComposer
return "{$leftPart} {$operator} ({$subQueryPart})";
}
if (str_ends_with($operatorOrm, 'any') || str_ends_with($operatorOrm, 'all')) {
throw new RuntimeException("ANY/ALL operators can be used only with sub-query.");
}
if ($value instanceof Expression) {
$isNotValue = true;

View File

@@ -55,6 +55,18 @@ class PostgresqlQueryComposer extends BaseQueryComposer
'!=' => '<>',
'!*' => 'NOT ILIKE',
'*' => 'ILIKE',
'>=any' => '>= ANY',
'<=any' => '<= ANY',
'>any' => '> ANY',
'<any' => '< ANY',
'!=any' => '<> ANY',
'=any' => '= ANY',
'>=all' => '>= ALL',
'<=all' => '<= ALL',
'>all' => '> ALL',
'<all' => '< ALL',
'!=all' => '<> ALL',
'=all' => '= ALL',
];
/** @var array<string, string> */

View File

@@ -1210,6 +1210,58 @@ class MysqlQueryComposerTest extends \PHPUnit\Framework\TestCase
$this->assertEquals($expectedSql, $sql);
}
public function testSelectWithSubquery7()
{
$subQuery = SelectBuilder::create()
->from('Post')
->select('id')
->where(['name' => 'test'])
->build();
$sql = $this->query->composeSelect(
SelectBuilder::create()
->from('Post')
->select('id')
->where(
Comparison::greaterOrEqualAny(Expression::value('1'), $subQuery)
)
->build()
);
$expectedSql =
"SELECT post.id AS `id` FROM `post` ".
"WHERE '1' >= ANY (SELECT post.id AS `id` FROM `post` ".
"WHERE post.name = 'test' AND post.deleted = 0) AND post.deleted = 0";
$this->assertEquals($expectedSql, $sql);
}
public function testSelectWithSubquery8()
{
$subQuery = SelectBuilder::create()
->from('Post')
->select('id')
->where(['name' => 'test'])
->build();
$sql = $this->query->composeSelect(
SelectBuilder::create()
->from('Post')
->select('id')
->where(
Comparison::greaterOrEqualAll(Expression::value('1'), $subQuery)
)
->build()
);
$expectedSql =
"SELECT post.id AS `id` FROM `post` ".
"WHERE '1' >= ALL (SELECT post.id AS `id` FROM `post` ".
"WHERE post.name = 'test' AND post.deleted = 0) AND post.deleted = 0";
$this->assertEquals($expectedSql, $sql);
}
public function testSelectExists1(): void
{
$expectedSql =