Skip to content

Commit 943437e

Browse files
committed
feat: Add support for the NOT operator in SQL queries. Add new methods whereNot, andWhereNot, and orWhereNot
to the query builder by @msmakouz (#185)
2 parents 1f5b226 + f1543f9 commit 943437e

File tree

5 files changed

+418
-33
lines changed

5 files changed

+418
-33
lines changed

src/Driver/Compiler.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,12 @@ protected function where(QueryParameters $params, Quoter $q, array $tokens): str
422422

423423
// first condition in group/query, no any AND, OR required
424424
if ($activeGroup) {
425+
// first condition can have a `NOT` keyword (WHERE NOT ...)
426+
if (\str_contains(\strtoupper($boolean), 'NOT')) {
427+
$statement .= 'NOT';
428+
$statement .= ' ';
429+
}
430+
425431
// next conditions require AND or OR
426432
$activeGroup = false;
427433
} else {

src/Driver/CompilerInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ interface CompilerInterface
2727

2828
public const TOKEN_AND = '@AND';
2929
public const TOKEN_OR = '@OR';
30+
public const TOKEN_AND_NOT = '@AND NOT';
31+
public const TOKEN_OR_NOT = '@OR NOT';
3032

3133
/**
3234
* @param string $identifier

src/Query/Traits/TokenTrait.php

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,7 @@ protected function registerToken(string $boolean, array $params, array &$tokens,
5252
}
5353

5454
if (\count($complex) === 1) {
55-
$this->flattenWhere(
56-
$boolean === 'AND' ? CompilerInterface::TOKEN_AND : CompilerInterface::TOKEN_OR,
57-
$complex,
58-
$tokens,
59-
$wrapper
60-
);
55+
$this->flattenWhere($this->booleanToToken($boolean), $complex, $tokens, $wrapper);
6156
return;
6257
}
6358

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

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

177172
// Grouping identifier (@OR, @AND), MongoDB like style
178-
if ($token === CompilerInterface::TOKEN_AND || $token === CompilerInterface::TOKEN_OR) {
173+
if (
174+
$token === CompilerInterface::TOKEN_AND ||
175+
$token === CompilerInterface::TOKEN_OR ||
176+
$token === CompilerInterface::TOKEN_AND_NOT ||
177+
$token === CompilerInterface::TOKEN_OR_NOT
178+
) {
179179
$tokens[] = [$boolean, '('];
180180

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

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

268268
return $tokens;
269269
}
270+
271+
private function tokenToBoolean(string $token): string
272+
{
273+
return match ($token) {
274+
CompilerInterface::TOKEN_AND => 'AND',
275+
CompilerInterface::TOKEN_AND_NOT => 'AND NOT',
276+
CompilerInterface::TOKEN_OR_NOT => 'OR NOT',
277+
default => 'OR',
278+
};
279+
}
280+
281+
private function booleanToToken(string $boolean): string
282+
{
283+
return match ($boolean) {
284+
'AND' => CompilerInterface::TOKEN_AND,
285+
'AND NOT' => CompilerInterface::TOKEN_AND_NOT,
286+
'OR NOT' => CompilerInterface::TOKEN_OR_NOT,
287+
default => 'OR',
288+
};
289+
}
270290
}

src/Query/Traits/WhereTrait.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,69 @@ public function orWhere(mixed ...$args): self
8383
return $this;
8484
}
8585

86+
/**
87+
* Simple WHERE NOT condition with various set of arguments.
88+
*
89+
* @param mixed ...$args [(column, value), (column, operator, value)]
90+
*
91+
* @throws BuilderException
92+
*
93+
* @return $this|self
94+
*/
95+
public function whereNot(mixed ...$args): self
96+
{
97+
$this->registerToken(
98+
'AND NOT',
99+
$args,
100+
$this->whereTokens,
101+
$this->whereWrapper()
102+
);
103+
104+
return $this;
105+
}
106+
107+
/**
108+
* Simple AND WHERE NOT condition with various set of arguments.
109+
*
110+
* @param mixed ...$args [(column, value), (column, operator, value)]
111+
*
112+
* @throws BuilderException
113+
*
114+
* @return $this|self
115+
*/
116+
public function andWhereNot(mixed ...$args): self
117+
{
118+
$this->registerToken(
119+
'AND NOT',
120+
$args,
121+
$this->whereTokens,
122+
$this->whereWrapper()
123+
);
124+
125+
return $this;
126+
}
127+
128+
/**
129+
* Simple OR WHERE NOT condition with various set of arguments.
130+
*
131+
* @param mixed ...$args [(column, value), (column, operator, value)]
132+
*
133+
* @throws BuilderException
134+
*
135+
* @return $this|self
136+
*/
137+
public function orWhereNot(mixed ...$args): self
138+
{
139+
$this->registerToken(
140+
'OR NOT',
141+
$args,
142+
$this->whereTokens,
143+
$this->whereWrapper()
144+
);
145+
146+
return $this;
147+
}
148+
86149
/**
87150
* Convert various amount of where function arguments into valid where token.
88151
*

0 commit comments

Comments
 (0)