Skip to content

Commit 0b4d063

Browse files
authored
Merge pull request #147: add support for FragmentInterface in array conditions
Co-authored-by: roxblnfk <[email protected]>
2 parents be80d1c + 0abebe3 commit 0b4d063

File tree

6 files changed

+201
-31
lines changed

6 files changed

+201
-31
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# CHANGELOG
22

3+
v2.7.0 (04.12.2023)
4+
-------------------
5+
- Add `varbinary` support in MySQL; optimize `size` attribute by @msmakouz (#146)
6+
- Add the ability to use WHERE IN and WHERE NOT IN with array values
7+
The value sequence may contain `FragmentInterface` objets by @msmakouz and @roxblnfk (#147)
8+
39
v2.6.0 (02.11.2023)
410
-------------------
511
- Fix incorrect parameters processing for JOIN subqueries by @smelesh (#133)

src/Driver/Compiler.php

+27-8
Original file line numberDiff line numberDiff line change
@@ -475,15 +475,14 @@ protected function condition(QueryParameters $params, Quoter $q, array $context)
475475

476476
$placeholder = '?';
477477
if ($value->isArray()) {
478-
if ($operator === '=') {
479-
$operator = 'IN';
480-
} elseif ($operator === '!=') {
481-
$operator = 'NOT IN';
482-
}
478+
return $this->arrayToInOperator($params, $q, $value->getValue(), match (\strtoupper($operator)) {
479+
'IN', '=' => true,
480+
'NOT IN', '!=' => false,
481+
default => throw CompilerException\UnexpectedOperatorException::sequence($operator),
482+
});
483+
}
483484

484-
$placeholder = '(' . rtrim(str_repeat('? ,', count($value->getValue())), ', ') . ')';
485-
$params->push($value);
486-
} elseif ($value->isNull()) {
485+
if ($value->isNull()) {
487486
if ($operator === '=') {
488487
$operator = 'IS';
489488
} elseif ($operator === '!=') {
@@ -521,4 +520,24 @@ protected function optional(string $prefix, string $expression, string $postfix
521520

522521
return $prefix . $expression . $postfix;
523522
}
523+
524+
private function arrayToInOperator(QueryParameters $params, Quoter $q, array $values, bool $in): string
525+
{
526+
$operator = $in ? 'IN' : 'NOT IN';
527+
528+
$placeholders = $simpleParams = [];
529+
foreach ($values as $value) {
530+
if ($value instanceof FragmentInterface) {
531+
$placeholders[] = $this->fragment($params, $q, $value);
532+
} else {
533+
$placeholders[] = '?';
534+
$simpleParams[] = $value;
535+
}
536+
}
537+
if ($simpleParams !== []) {
538+
$params->push(new Parameter($simpleParams));
539+
}
540+
541+
return \sprintf('%s(%s)', $operator, \implode(',', $placeholders));
542+
}
524543
}

src/Driver/CompilerCache.php

+17-2
Original file line numberDiff line numberDiff line change
@@ -316,12 +316,27 @@ private function hashParam(QueryParameters $params, ParameterInterface $param):
316316
return 'N';
317317
}
318318

319-
$params->push($param);
320-
321319
if ($param->isArray()) {
320+
$simpleParams = [];
321+
foreach ($param->getValue() as $value) {
322+
if ($value instanceof FragmentInterface) {
323+
foreach ($value->getTokens()['parameters'] as $fragmentParam) {
324+
$params->push($fragmentParam);
325+
}
326+
} else {
327+
$simpleParams[] = $value;
328+
}
329+
}
330+
331+
if ($simpleParams !== []) {
332+
$params->push(new Parameter($simpleParams));
333+
}
334+
322335
return 'A' . count($param->getValue());
323336
}
324337

338+
$params->push($param);
339+
325340
return '?';
326341
}
327342
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/**
4+
* This file is part of Cycle ORM package.
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace Cycle\Database\Exception\CompilerException;
13+
14+
use Cycle\Database\Exception\CompilerException;
15+
16+
class UnexpectedOperatorException extends CompilerException
17+
{
18+
/**
19+
* Exception for the value sequence (IN or NOT IN operator).
20+
*
21+
* @param string $operator User-provided operator.
22+
*/
23+
public static function sequence(string $operator): self
24+
{
25+
return new self(
26+
\sprintf(
27+
'Unable to compile query, unexpected operator `%s` provided for a value sequence. %s',
28+
$operator,
29+
'Allowed operators: `IN`, `NOT IN` (or `=`, `!=` as sugar).'
30+
)
31+
);
32+
}
33+
}

src/Query/Traits/WhereTrait.php

+5-10
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@
1313

1414
use Closure;
1515
use Cycle\Database\Exception\BuilderException;
16-
use Cycle\Database\Injection\FragmentInterface;
17-
use Cycle\Database\Injection\Parameter;
18-
use Cycle\Database\Injection\ParameterInterface;
16+
use Cycle\Database\Injection\FragmentInterface as Fragment;
17+
use Cycle\Database\Injection\ParameterInterface as Parameter;
1918

2019
trait WhereTrait
2120
{
@@ -108,12 +107,8 @@ abstract protected function registerToken(
108107
*/
109108
protected function whereWrapper(): Closure
110109
{
111-
return static function ($parameter) {
112-
\is_array($parameter) and throw new BuilderException('Arrays must be wrapped with Parameter instance.');
113-
114-
return !$parameter instanceof ParameterInterface && !$parameter instanceof FragmentInterface
115-
? new Parameter($parameter)
116-
: $parameter;
117-
};
110+
return static fn ($parameter) => $parameter instanceof Parameter || $parameter instanceof Fragment
111+
? $parameter
112+
: new \Cycle\Database\Injection\Parameter($parameter);
118113
}
119114
}

tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php

+113-11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Cycle\Database\Tests\Functional\Driver\Common\Query;
66

77
use Cycle\Database\Exception\BuilderException;
8+
use Cycle\Database\Exception\CompilerException\UnexpectedOperatorException;
89
use Cycle\Database\Injection\Expression;
910
use Cycle\Database\Injection\Fragment;
1011
use Cycle\Database\Injection\Parameter;
@@ -334,12 +335,13 @@ public function testSelectWithWhereOrWhere(): void
334335

335336
public function testSelectInvalidArrayArgument(): void
336337
{
337-
$this->expectException(BuilderException::class);
338+
$this->expectException(UnexpectedOperatorException::class);
338339

339340
$this->database->select()->distinct()
340341
->from(['users'])
341342
->where('name', 'Anton')
342-
->orWhere('id', 'like', [1, 2, 3]);
343+
->orWhere('id', 'like', [1, 2, 3])
344+
->sqlStatement();
343345
}
344346

345347
public function testSelectWithWhereOrWhereAndWhere(): void
@@ -1917,16 +1919,16 @@ public function testInOperatorWithBadArrayParameter(): void
19171919

19181920
public function testBadArrayParameterInShortWhere(): void
19191921
{
1920-
$this->expectException(BuilderException::class);
1921-
$this->expectExceptionMessage('Arrays must be wrapped with Parameter instance');
1922+
$this->expectException(UnexpectedOperatorException::class);
19221923

1923-
$this->database->select()
1924-
->from(['users'])
1925-
->where(
1926-
[
1927-
'status' => ['LIKE' => ['active', 'blocked']],
1928-
]
1929-
);
1924+
$this->database
1925+
->select()
1926+
->from(['users'])
1927+
->where(
1928+
[
1929+
'status' => ['LIKE' => ['active', 'blocked']],
1930+
]
1931+
)->sqlStatement();
19301932
}
19311933

19321934
public function testGoodArrayParameter(): void
@@ -2165,4 +2167,104 @@ public function testSelectWithFragmentedColumns(): void
21652167
$select
21662168
);
21672169
}
2170+
2171+
public function testWhereInWithoutSpecifiedOperator(): void
2172+
{
2173+
$select = $this->database
2174+
->select()
2175+
->from(['users'])
2176+
->where(
2177+
'uuid',
2178+
new Parameter(['12345678-1234-1234-1234-123456789012', '12345678-1234-1234-1234-123456789013'])
2179+
);
2180+
2181+
$this->assertSameQuery('SELECT * FROM {users} WHERE {uuid} IN (?, ?)', $select);
2182+
2183+
$this->assertSameParameters(
2184+
['12345678-1234-1234-1234-123456789012', '12345678-1234-1234-1234-123456789013'],
2185+
$select,
2186+
);
2187+
}
2188+
2189+
public function testWhereInWithEqualSpecifiedOperator(): void
2190+
{
2191+
$select = $this->database
2192+
->select()
2193+
->from(['users'])
2194+
->where(
2195+
'uuid',
2196+
'=',
2197+
new Parameter(['12345678-1234-1234-1234-123456789012', '12345678-1234-1234-1234-123456789013'])
2198+
)->orWhere(
2199+
'uuid',
2200+
'=',
2201+
['23456789-1234-1234-1234-123456789012', '23456789-1234-1234-1234-123456789013']
2202+
);
2203+
2204+
$this->assertSameQuery('SELECT * FROM {users} WHERE {uuid} IN (?, ?) OR {uuid} IN (?, ?)', $select);
2205+
2206+
$this->assertSameParameters(
2207+
[
2208+
'12345678-1234-1234-1234-123456789012',
2209+
'12345678-1234-1234-1234-123456789013',
2210+
'23456789-1234-1234-1234-123456789012',
2211+
'23456789-1234-1234-1234-123456789013',
2212+
],
2213+
$select,
2214+
);
2215+
}
2216+
2217+
public function testWhereInWithNotEqualSpecifiedOperator(): void
2218+
{
2219+
$select = $this->database
2220+
->select()
2221+
->from(['users'])
2222+
->where(
2223+
'uuid',
2224+
'!=',
2225+
new Parameter(['12345678-1234-1234-1234-123456789012', '12345678-1234-1234-1234-123456789013'])
2226+
)->orWhere(
2227+
'uuid',
2228+
'!=',
2229+
['23456789-1234-1234-1234-123456789012', '23456789-1234-1234-1234-123456789013']
2230+
);
2231+
2232+
$this->assertSameQuery('SELECT * FROM {users} WHERE {uuid} NOT IN (?, ?) OR {uuid} NOT IN (?, ?)', $select);
2233+
2234+
$this->assertSameParameters(
2235+
[
2236+
'12345678-1234-1234-1234-123456789012',
2237+
'12345678-1234-1234-1234-123456789013',
2238+
'23456789-1234-1234-1234-123456789012',
2239+
'23456789-1234-1234-1234-123456789013',
2240+
],
2241+
$select,
2242+
);
2243+
}
2244+
2245+
public function testFragmentInWhereInClause(): void
2246+
{
2247+
$select = $this->database
2248+
->select()
2249+
->from(['users'])
2250+
->where('uuid', new Fragment('UUID_TO_BIN(?)', '12345678-1234-1234-1234-123456789012'))
2251+
->andWhere('uuid', 'IN', [
2252+
new Fragment('UUID_TO_BIN(?)', '12345678-1234-1234-1234-123456789013'),
2253+
new Fragment('UUID_TO_BIN(?)', '12345678-1234-1234-1234-123456789014'),
2254+
]);
2255+
2256+
$this->assertSameQuery(
2257+
'SELECT * FROM {users} WHERE {uuid} = UUID_TO_BIN (?) AND {uuid} IN (UUID_TO_BIN (?), UUID_TO_BIN (?))',
2258+
$select
2259+
);
2260+
2261+
$this->assertSameParameters(
2262+
[
2263+
'12345678-1234-1234-1234-123456789012',
2264+
'12345678-1234-1234-1234-123456789013',
2265+
'12345678-1234-1234-1234-123456789014',
2266+
],
2267+
$select
2268+
);
2269+
}
21682270
}

0 commit comments

Comments
 (0)