Skip to content

Commit

Permalink
Fix nested not conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
msmakouz committed Apr 3, 2024
1 parent e18adda commit 277baa9
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 9 deletions.
2 changes: 2 additions & 0 deletions src/Driver/CompilerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ interface CompilerInterface

public const TOKEN_AND = '@AND';
public const TOKEN_OR = '@OR';
public const TOKEN_AND_NOT = '@AND NOT';
public const TOKEN_OR_NOT = '@OR NOT';

/**
* @param string $identifier
Expand Down
38 changes: 29 additions & 9 deletions src/Query/Traits/TokenTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,7 @@ protected function registerToken(string $boolean, array $params, array &$tokens,
}

if (\count($complex) === 1) {
$this->flattenWhere(
$boolean === 'AND' ? CompilerInterface::TOKEN_AND : CompilerInterface::TOKEN_OR,
$complex,
$tokens,
$wrapper
);
$this->flattenWhere($this->booleanToToken($boolean), $complex, $tokens, $wrapper);
return;
}

Expand Down Expand Up @@ -161,7 +156,7 @@ protected function registerToken(string $boolean, array $params, array &$tokens,
*/
private function flattenWhere(string $grouper, array $where, array &$tokens, callable $wrapper): void
{
$boolean = ($grouper === CompilerInterface::TOKEN_AND ? 'AND' : 'OR');
$boolean = $this->tokenToBoolean($grouper);

foreach ($where as $key => $value) {
// Support for closures
Expand All @@ -175,7 +170,12 @@ private function flattenWhere(string $grouper, array $where, array &$tokens, cal
$token = strtoupper($key);

// Grouping identifier (@OR, @AND), MongoDB like style
if ($token === CompilerInterface::TOKEN_AND || $token === CompilerInterface::TOKEN_OR) {
if (
$token === CompilerInterface::TOKEN_AND ||
$token === CompilerInterface::TOKEN_OR ||
$token === CompilerInterface::TOKEN_AND_NOT ||
$token === CompilerInterface::TOKEN_OR_NOT
) {
$tokens[] = [$boolean, '('];

foreach ($value as $nested) {
Expand All @@ -184,7 +184,7 @@ private function flattenWhere(string $grouper, array $where, array &$tokens, cal
continue;
}

$tokens[] = [$token === CompilerInterface::TOKEN_AND ? 'AND' : 'OR', '('];
$tokens[] = [$this->tokenToBoolean($token), '('];
$this->flattenWhere(CompilerInterface::TOKEN_AND, $nested, $tokens, $wrapper);
$tokens[] = ['', ')'];
}
Expand Down Expand Up @@ -267,4 +267,24 @@ private function pushCondition(string $innerJoiner, string $key, array $where, &

return $tokens;
}

private function tokenToBoolean(string $token): string
{
return match ($token) {
CompilerInterface::TOKEN_AND => 'AND',
CompilerInterface::TOKEN_AND_NOT => 'AND NOT',
CompilerInterface::TOKEN_OR_NOT => 'OR NOT',
default => 'OR',
};
}

private function booleanToToken(string $boolean): string
{
return match ($boolean) {
'AND' => CompilerInterface::TOKEN_AND,
'AND NOT' => CompilerInterface::TOKEN_AND_NOT,
'OR NOT' => CompilerInterface::TOKEN_OR_NOT,
default => 'OR',
};
}
}
173 changes: 173 additions & 0 deletions tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,27 @@ public function testAndShortWhereOR(): void
);
}

public function testAndShortWhereOrNot(): void
{
$select = $this->database
->select()
->from(['users'])
->where(['name' => 'Anton'])
->andWhere(
[
'@or not' => [
['value' => 1],
['value' => ['>' => 12]],
],
]
);

$this->assertSameQuery(
'SELECT * FROM {users} WHERE {name} = ? AND (NOT {value} = ? OR NOT {value} > ?)',
$select
);
}

public function testOrShortWhereOR(): void
{
$select = $this->database
Expand All @@ -609,6 +630,27 @@ public function testOrShortWhereOR(): void
);
}

public function testOrShortWhereOrNot(): void
{
$select = $this->database
->select()
->from(['users'])
->where(['name' => 'Anton'])
->orWhere(
[
'@or not' => [
['value' => 1],
['value' => ['>' => 12]],
],
]
);

$this->assertSameQuery(
'SELECT * FROM {users} WHERE {name} = ? OR (NOT {value} = ? OR NOT {value} > ?)',
$select
);
}

public function testAndShortWhereAND(): void
{
$select = $this->database
Expand All @@ -630,6 +672,27 @@ public function testAndShortWhereAND(): void
);
}

public function testAndShortWhereAndNot(): void
{
$select = $this->database
->select()
->from(['users'])
->where(['name' => 'Anton'])
->andWhere(
[
'@and not' => [
['value' => 1],
['value' => ['>' => 12]],
],
]
);

$this->assertSameQuery(
'SELECT * FROM {users} WHERE {name} = ? AND (NOT {value} = ? AND NOT {value} > ?)',
$select
);
}

public function testOrShortWhereAND(): void
{
$select = $this->database
Expand All @@ -651,6 +714,27 @@ public function testOrShortWhereAND(): void
);
}

public function testOrShortWhereAndNot(): void
{
$select = $this->database
->select()
->from(['users'])
->where(['name' => 'Anton'])
->orWhere(
[
'@and not' => [
['value' => 1],
['value' => ['>' => 12]],
],
]
);

$this->assertSameQuery(
'SELECT * FROM {users} WHERE {name} = ? OR (NOT {value} = ? AND NOT {value} > ?)',
$select
);
}

public function testBadShortExpression(): void
{
$this->expectException(BuilderException::class);
Expand Down Expand Up @@ -2412,4 +2496,93 @@ public function testPrefixedSelectWithFullySpecificColumnNameInWhereNotButAliase
$select
);
}

public function testShortWhereNotMultiple(): void
{
$select = $this->database
->select()
->from(['users'])
->whereNot([
'name' => 'John Doe',
'value' => 1,
]);

$this->assertSameQuery('SELECT * FROM {users} WHERE NOT ({name} = ? AND {value} = ?)', $select);
}

public function testAndWhereNotWithArrayOr(): void
{
$select = $this->database
->select()
->from(['users'])
->where(['name' => 'John Doe'])
->andWhereNot([
'@or' => [
['id' => ['between' => [10, 100]], 'name' => 'John Doe'],
['status' => 'disabled']
]
]);

$this->assertSameQuery(
'SELECT * FROM {users} WHERE {name} = ? AND NOT (({id} BETWEEN ? AND ? AND {name} = ?) OR {status} = ?)',
$select
);
}

public function testAndWhereNotWithArrayAnd(): void
{
$select = $this->database
->select()
->from(['users'])
->where(['name' => 'John Doe'])
->andWhereNot([
'@and' => [
['id' => ['between' => [10, 100]], 'name' => 'John Doe'],
['status' => 'disabled']
]
]);

$this->assertSameQuery(
'SELECT * FROM {users} WHERE {name} = ? AND NOT (({id} BETWEEN ? AND ? AND {name} = ?) AND {status} = ?)',
$select
);
}

public function testOrWhereNotWithArrayOr(): void
{
$select = $this->database
->select()
->from(['users'])
->where(['name' => 'John Doe'])
->orWhereNot([
'@or' => [
['id' => ['between' => [10, 100]], 'name' => 'John Doe'],
['status' => 'disabled']
]
]);

$this->assertSameQuery(
'SELECT * FROM {users} WHERE {name} = ? OR NOT (({id} BETWEEN ? AND ? AND {name} = ?) OR {status} = ?)',
$select
);
}

public function testOrWhereNotWithArrayAnd(): void
{
$select = $this->database
->select()
->from(['users'])
->where(['name' => 'John Doe'])
->orWhereNot([
'@and' => [
['id' => ['between' => [10, 100]], 'name' => 'John Doe'],
['status' => 'disabled']
]
]);

$this->assertSameQuery(
'SELECT * FROM {users} WHERE {name} = ? OR NOT (({id} BETWEEN ? AND ? AND {name} = ?) AND {status} = ?)',
$select
);
}
}

0 comments on commit 277baa9

Please sign in to comment.