From d4583daf779d12cac70db2ff64902fc9ebacd134 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 28 Nov 2023 19:46:22 +0200 Subject: [PATCH 1/5] Add Fragment support in array condition --- src/Driver/Compiler.php | 31 ++++++++++++++----- src/Driver/CompilerCache.php | 19 ++++++++++-- .../Driver/Common/Query/SelectQueryTest.php | 26 ++++++++++++++++ 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/src/Driver/Compiler.php b/src/Driver/Compiler.php index ffd0d0c9..e8850178 100644 --- a/src/Driver/Compiler.php +++ b/src/Driver/Compiler.php @@ -475,15 +475,10 @@ protected function condition(QueryParameters $params, Quoter $q, array $context) $placeholder = '?'; if ($value->isArray()) { - if ($operator === '=') { - $operator = 'IN'; - } elseif ($operator === '!=') { - $operator = 'NOT IN'; - } + return $this->compileArrayCondition($value->getValue(), $params, $q, $operator === 'IN'); + } - $placeholder = '(' . rtrim(str_repeat('? ,', count($value->getValue())), ', ') . ')'; - $params->push($value); - } elseif ($value->isNull()) { + if ($value->isNull()) { if ($operator === '=') { $operator = 'IS'; } elseif ($operator === '!=') { @@ -521,4 +516,24 @@ protected function optional(string $prefix, string $expression, string $postfix return $prefix . $expression . $postfix; } + + private function compileArrayCondition(array $values, QueryParameters $params, Quoter $q, bool $in): string + { + $operator = $in ? 'IN' : 'NOT IN'; + + $placeholders = $simpleParams = []; + foreach ($values as $value) { + if ($value instanceof FragmentInterface) { + $placeholders[] = $this->fragment($params, $q, $value); + } else { + $placeholders[] = '?'; + $simpleParams[] = $value; + } + } + if ($simpleParams !== []) { + $params->push(new Parameter($simpleParams)); + } + + return \sprintf('%s (%s)', $operator, \implode(', ', $placeholders)); + } } diff --git a/src/Driver/CompilerCache.php b/src/Driver/CompilerCache.php index 343e15f8..4340c0d7 100644 --- a/src/Driver/CompilerCache.php +++ b/src/Driver/CompilerCache.php @@ -316,12 +316,27 @@ private function hashParam(QueryParameters $params, ParameterInterface $param): return 'N'; } - $params->push($param); - if ($param->isArray()) { + $simpleParams = []; + foreach ($param->getValue() as $value) { + if ($value instanceof FragmentInterface) { + foreach ($value->getTokens()['parameters'] as $fragmentParam) { + $params->push($fragmentParam); + } + } else { + $simpleParams[] = $value; + } + } + + if ($simpleParams !== []) { + $params->push(new Parameter($simpleParams)); + } + return 'A' . count($param->getValue()); } + $params->push($param); + return '?'; } } diff --git a/tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php b/tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php index a0aa84c8..ce30a7dd 100644 --- a/tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php +++ b/tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php @@ -2165,4 +2165,30 @@ public function testSelectWithFragmentedColumns(): void $select ); } + + public function testFragmentInWhereInClause(): void + { + $select = $this->database + ->select() + ->from(['users']) + ->where('uuid', new Fragment('UUID_TO_BIN(?)', '12345678-1234-1234-1234-123456789012')) + ->andWhere('uuid', 'IN', [ + new Fragment('UUID_TO_BIN(?)', '12345678-1234-1234-1234-123456789013'), + new Fragment('UUID_TO_BIN(?)', '12345678-1234-1234-1234-123456789014') + ]); + + $this->assertSameQuery( + 'SELECT * FROM {users} WHERE {uuid} = UUID_TO_BIN (?) AND {uuid} IN (UUID_TO_BIN (?), UUID_TO_BIN (?))', + $select + ); + + $this->assertSameParameters( + [ + '12345678-1234-1234-1234-123456789012', + '12345678-1234-1234-1234-123456789013', + '12345678-1234-1234-1234-123456789014', + ], + $select + ); + } } From f0173e9560da3c9ca8cd5e2db3a0b669139a4fb0 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 28 Nov 2023 17:46:45 +0000 Subject: [PATCH 2/5] Apply fixes from StyleCI --- .../Database/Functional/Driver/Common/Query/SelectQueryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php b/tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php index ce30a7dd..c0cc74a3 100644 --- a/tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php +++ b/tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php @@ -2174,7 +2174,7 @@ public function testFragmentInWhereInClause(): void ->where('uuid', new Fragment('UUID_TO_BIN(?)', '12345678-1234-1234-1234-123456789012')) ->andWhere('uuid', 'IN', [ new Fragment('UUID_TO_BIN(?)', '12345678-1234-1234-1234-123456789013'), - new Fragment('UUID_TO_BIN(?)', '12345678-1234-1234-1234-123456789014') + new Fragment('UUID_TO_BIN(?)', '12345678-1234-1234-1234-123456789014'), ]); $this->assertSameQuery( From 32537f4295a69a68e3dd2de8ca3b8e54a3f655f5 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Wed, 29 Nov 2023 18:35:33 +0200 Subject: [PATCH 3/5] Minimize result query --- src/Driver/Compiler.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Driver/Compiler.php b/src/Driver/Compiler.php index e8850178..d5fbc80e 100644 --- a/src/Driver/Compiler.php +++ b/src/Driver/Compiler.php @@ -475,7 +475,7 @@ protected function condition(QueryParameters $params, Quoter $q, array $context) $placeholder = '?'; if ($value->isArray()) { - return $this->compileArrayCondition($value->getValue(), $params, $q, $operator === 'IN'); + return $this->arrayToInOperator($params, $q, $value->getValue(), $operator === 'IN'); } if ($value->isNull()) { @@ -517,7 +517,7 @@ protected function optional(string $prefix, string $expression, string $postfix return $prefix . $expression . $postfix; } - private function compileArrayCondition(array $values, QueryParameters $params, Quoter $q, bool $in): string + private function arrayToInOperator(QueryParameters $params, Quoter $q, array $values, bool $in): string { $operator = $in ? 'IN' : 'NOT IN'; @@ -534,6 +534,6 @@ private function compileArrayCondition(array $values, QueryParameters $params, Q $params->push(new Parameter($simpleParams)); } - return \sprintf('%s (%s)', $operator, \implode(', ', $placeholders)); + return \sprintf('%s(%s)', $operator, \implode(',', $placeholders)); } } From cc23e1ea2f38431b32b1a3af3561f86b1b9f27d8 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Wed, 29 Nov 2023 19:16:56 +0200 Subject: [PATCH 4/5] Add tests with different operators --- src/Driver/Compiler.php | 2 +- .../Driver/Common/Query/SelectQueryTest.php | 56 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/Driver/Compiler.php b/src/Driver/Compiler.php index d5fbc80e..f08dda93 100644 --- a/src/Driver/Compiler.php +++ b/src/Driver/Compiler.php @@ -475,7 +475,7 @@ protected function condition(QueryParameters $params, Quoter $q, array $context) $placeholder = '?'; if ($value->isArray()) { - return $this->arrayToInOperator($params, $q, $value->getValue(), $operator === 'IN'); + return $this->arrayToInOperator($params, $q, $value->getValue(), $operator === 'IN' || $operator === '='); } if ($value->isNull()) { diff --git a/tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php b/tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php index c0cc74a3..8c3caa40 100644 --- a/tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php +++ b/tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php @@ -2166,6 +2166,62 @@ public function testSelectWithFragmentedColumns(): void ); } + public function testWhereInWithoutSpecifiedOperator(): void + { + $select = $this->database + ->select() + ->from(['users']) + ->where( + 'uuid', + new Parameter(['12345678-1234-1234-1234-123456789012', '12345678-1234-1234-1234-123456789013']) + ); + + $this->assertSameQuery('SELECT * FROM {users} WHERE {uuid} IN (?, ?)', $select); + + $this->assertSameParameters( + ['12345678-1234-1234-1234-123456789012', '12345678-1234-1234-1234-123456789013'], + $select, + ); + } + + public function testWhereInWithEqualSpecifiedOperator(): void + { + $select = $this->database + ->select() + ->from(['users']) + ->where( + 'uuid', + '=', + new Parameter(['12345678-1234-1234-1234-123456789012', '12345678-1234-1234-1234-123456789013']) + ); + + $this->assertSameQuery('SELECT * FROM {users} WHERE {uuid} IN (?, ?)', $select); + + $this->assertSameParameters( + ['12345678-1234-1234-1234-123456789012', '12345678-1234-1234-1234-123456789013'], + $select, + ); + } + + public function testWhereInWithNotEqualSpecifiedOperator(): void + { + $select = $this->database + ->select() + ->from(['users']) + ->where( + 'uuid', + '!=', + new Parameter(['12345678-1234-1234-1234-123456789012', '12345678-1234-1234-1234-123456789013']) + ); + + $this->assertSameQuery('SELECT * FROM {users} WHERE {uuid} NOT IN (?, ?)', $select); + + $this->assertSameParameters( + ['12345678-1234-1234-1234-123456789012', '12345678-1234-1234-1234-123456789013'], + $select, + ); + } + public function testFragmentInWhereInClause(): void { $select = $this->database From 0abebe364b163769d7c32b67f980533d96195b7a Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 4 Dec 2023 19:57:53 +0400 Subject: [PATCH 5/5] Allow using arrays in sequence operations without wrapping with Parameter; Update changelog --- CHANGELOG.md | 6 +++ src/Driver/Compiler.php | 6 ++- .../UnexpectedOperatorException.php | 33 ++++++++++++ src/Query/Traits/WhereTrait.php | 15 ++---- .../Driver/Common/Query/SelectQueryTest.php | 50 +++++++++++++------ 5 files changed, 84 insertions(+), 26 deletions(-) create mode 100644 src/Exception/CompilerException/UnexpectedOperatorException.php diff --git a/CHANGELOG.md b/CHANGELOG.md index d8c301c7..cc7ba1b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +v2.7.0 (04.12.2023) +------------------- +- Add `varbinary` support in MySQL; optimize `size` attribute by @msmakouz (#146) +- Add the ability to use WHERE IN and WHERE NOT IN with array values + The value sequence may contain `FragmentInterface` objets by @msmakouz and @roxblnfk (#147) + v2.6.0 (02.11.2023) ------------------- - Fix incorrect parameters processing for JOIN subqueries by @smelesh (#133) diff --git a/src/Driver/Compiler.php b/src/Driver/Compiler.php index f08dda93..e05961aa 100644 --- a/src/Driver/Compiler.php +++ b/src/Driver/Compiler.php @@ -475,7 +475,11 @@ protected function condition(QueryParameters $params, Quoter $q, array $context) $placeholder = '?'; if ($value->isArray()) { - return $this->arrayToInOperator($params, $q, $value->getValue(), $operator === 'IN' || $operator === '='); + return $this->arrayToInOperator($params, $q, $value->getValue(), match (\strtoupper($operator)) { + 'IN', '=' => true, + 'NOT IN', '!=' => false, + default => throw CompilerException\UnexpectedOperatorException::sequence($operator), + }); } if ($value->isNull()) { diff --git a/src/Exception/CompilerException/UnexpectedOperatorException.php b/src/Exception/CompilerException/UnexpectedOperatorException.php new file mode 100644 index 00000000..fae6306b --- /dev/null +++ b/src/Exception/CompilerException/UnexpectedOperatorException.php @@ -0,0 +1,33 @@ + $parameter instanceof Parameter || $parameter instanceof Fragment + ? $parameter + : new \Cycle\Database\Injection\Parameter($parameter); } } diff --git a/tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php b/tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php index 8c3caa40..90f69101 100644 --- a/tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php +++ b/tests/Database/Functional/Driver/Common/Query/SelectQueryTest.php @@ -5,6 +5,7 @@ namespace Cycle\Database\Tests\Functional\Driver\Common\Query; use Cycle\Database\Exception\BuilderException; +use Cycle\Database\Exception\CompilerException\UnexpectedOperatorException; use Cycle\Database\Injection\Expression; use Cycle\Database\Injection\Fragment; use Cycle\Database\Injection\Parameter; @@ -334,12 +335,13 @@ public function testSelectWithWhereOrWhere(): void public function testSelectInvalidArrayArgument(): void { - $this->expectException(BuilderException::class); + $this->expectException(UnexpectedOperatorException::class); $this->database->select()->distinct() ->from(['users']) ->where('name', 'Anton') - ->orWhere('id', 'like', [1, 2, 3]); + ->orWhere('id', 'like', [1, 2, 3]) + ->sqlStatement(); } public function testSelectWithWhereOrWhereAndWhere(): void @@ -1917,16 +1919,16 @@ public function testInOperatorWithBadArrayParameter(): void public function testBadArrayParameterInShortWhere(): void { - $this->expectException(BuilderException::class); - $this->expectExceptionMessage('Arrays must be wrapped with Parameter instance'); + $this->expectException(UnexpectedOperatorException::class); - $this->database->select() - ->from(['users']) - ->where( - [ - 'status' => ['LIKE' => ['active', 'blocked']], - ] - ); + $this->database + ->select() + ->from(['users']) + ->where( + [ + 'status' => ['LIKE' => ['active', 'blocked']], + ] + )->sqlStatement(); } public function testGoodArrayParameter(): void @@ -2193,12 +2195,21 @@ public function testWhereInWithEqualSpecifiedOperator(): void 'uuid', '=', new Parameter(['12345678-1234-1234-1234-123456789012', '12345678-1234-1234-1234-123456789013']) + )->orWhere( + 'uuid', + '=', + ['23456789-1234-1234-1234-123456789012', '23456789-1234-1234-1234-123456789013'] ); - $this->assertSameQuery('SELECT * FROM {users} WHERE {uuid} IN (?, ?)', $select); + $this->assertSameQuery('SELECT * FROM {users} WHERE {uuid} IN (?, ?) OR {uuid} IN (?, ?)', $select); $this->assertSameParameters( - ['12345678-1234-1234-1234-123456789012', '12345678-1234-1234-1234-123456789013'], + [ + '12345678-1234-1234-1234-123456789012', + '12345678-1234-1234-1234-123456789013', + '23456789-1234-1234-1234-123456789012', + '23456789-1234-1234-1234-123456789013', + ], $select, ); } @@ -2212,12 +2223,21 @@ public function testWhereInWithNotEqualSpecifiedOperator(): void 'uuid', '!=', new Parameter(['12345678-1234-1234-1234-123456789012', '12345678-1234-1234-1234-123456789013']) + )->orWhere( + 'uuid', + '!=', + ['23456789-1234-1234-1234-123456789012', '23456789-1234-1234-1234-123456789013'] ); - $this->assertSameQuery('SELECT * FROM {users} WHERE {uuid} NOT IN (?, ?)', $select); + $this->assertSameQuery('SELECT * FROM {users} WHERE {uuid} NOT IN (?, ?) OR {uuid} NOT IN (?, ?)', $select); $this->assertSameParameters( - ['12345678-1234-1234-1234-123456789012', '12345678-1234-1234-1234-123456789013'], + [ + '12345678-1234-1234-1234-123456789012', + '12345678-1234-1234-1234-123456789013', + '23456789-1234-1234-1234-123456789012', + '23456789-1234-1234-1234-123456789013', + ], $select, ); }