Skip to content

Commit

Permalink
Merge pull request #185 from cycle/feature/where-not
Browse files Browse the repository at this point in the history
feat: Add support for the `NOT` operator in SQL queries. Add new methods `whereNot`, `andWhereNot`, and `orWhereNot`
to the query builder by @msmakouz (#185)
  • Loading branch information
roxblnfk authored Apr 4, 2024
2 parents 1f5b226 + f1543f9 commit dafbe73
Show file tree
Hide file tree
Showing 5 changed files with 418 additions and 33 deletions.
6 changes: 6 additions & 0 deletions src/Driver/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,12 @@ protected function where(QueryParameters $params, Quoter $q, array $tokens): str

// first condition in group/query, no any AND, OR required
if ($activeGroup) {
// first condition can have a `NOT` keyword (WHERE NOT ...)
if (\str_contains(\strtoupper($boolean), 'NOT')) {
$statement .= 'NOT';
$statement .= ' ';
}

// next conditions require AND or OR
$activeGroup = false;
} else {
Expand Down
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',
};
}
}
63 changes: 63 additions & 0 deletions src/Query/Traits/WhereTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,69 @@ public function orWhere(mixed ...$args): self
return $this;
}

/**
* Simple WHERE NOT condition with various set of arguments.
*
* @param mixed ...$args [(column, value), (column, operator, value)]
*
* @throws BuilderException
*
* @return $this|self
*/
public function whereNot(mixed ...$args): self
{
$this->registerToken(
'AND NOT',
$args,
$this->whereTokens,
$this->whereWrapper()
);

return $this;
}

/**
* Simple AND WHERE NOT condition with various set of arguments.
*
* @param mixed ...$args [(column, value), (column, operator, value)]
*
* @throws BuilderException
*
* @return $this|self
*/
public function andWhereNot(mixed ...$args): self
{
$this->registerToken(
'AND NOT',
$args,
$this->whereTokens,
$this->whereWrapper()
);

return $this;
}

/**
* Simple OR WHERE NOT condition with various set of arguments.
*
* @param mixed ...$args [(column, value), (column, operator, value)]
*
* @throws BuilderException
*
* @return $this|self
*/
public function orWhereNot(mixed ...$args): self
{
$this->registerToken(
'OR NOT',
$args,
$this->whereTokens,
$this->whereWrapper()
);

return $this;
}

/**
* Convert various amount of where function arguments into valid where token.
*
Expand Down
Loading

0 comments on commit dafbe73

Please sign in to comment.