From aa32f746b445b369173c64282332cd4b93118758 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sat, 29 Jun 2024 14:53:57 +0700 Subject: [PATCH 1/7] Add json overlaps condition builders --- src/Builder/JsonOverlapsConditionBuilder.php | 44 +++++++++++++++++++ src/DQLQueryBuilder.php | 3 ++ tests/QueryBuilderTest.php | 45 ++++++++++++++++++++ tests/Support/Fixture/mysql.sql | 12 ++++++ tests/Support/TestTrait.php | 9 ++++ 5 files changed, 113 insertions(+) create mode 100644 src/Builder/JsonOverlapsConditionBuilder.php diff --git a/src/Builder/JsonOverlapsConditionBuilder.php b/src/Builder/JsonOverlapsConditionBuilder.php new file mode 100644 index 000000000..5e8645766 --- /dev/null +++ b/src/Builder/JsonOverlapsConditionBuilder.php @@ -0,0 +1,44 @@ +prepareColumn($expression->getColumn()); + $values = $expression->getValues(); + + if (!$values instanceof ExpressionInterface) { + $values = new JsonExpression($values); + } + + $values = $this->queryBuilder->buildExpression($values, $params); + + return "JSON_OVERLAPS($column, $values)"; + } +} diff --git a/src/DQLQueryBuilder.php b/src/DQLQueryBuilder.php index 64abf7557..d4604ae6a 100644 --- a/src/DQLQueryBuilder.php +++ b/src/DQLQueryBuilder.php @@ -9,7 +9,9 @@ use Yiisoft\Db\Expression\JsonExpression; use Yiisoft\Db\Mysql\Builder\ExpressionBuilder; use Yiisoft\Db\Mysql\Builder\JsonExpressionBuilder; +use Yiisoft\Db\Mysql\Builder\JsonOverlapsConditionBuilder; use Yiisoft\Db\QueryBuilder\AbstractDQLQueryBuilder; +use Yiisoft\Db\QueryBuilder\Condition\JsonOverlapsCondition; use function array_merge; use function ctype_digit; @@ -84,6 +86,7 @@ protected function defaultExpressionBuilders(): array parent::defaultExpressionBuilders(), [ JsonExpression::class => JsonExpressionBuilder::class, + JsonOverlapsCondition::class => JsonOverlapsConditionBuilder::class, Expression::class => ExpressionBuilder::class, ] ); diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index 811901c1c..dce599656 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -13,6 +13,7 @@ use Yiisoft\Db\Mysql\Tests\Support\TestTrait; use Yiisoft\Db\Query\Query; use Yiisoft\Db\Query\QueryInterface; +use Yiisoft\Db\QueryBuilder\Condition\JsonOverlapsCondition; use Yiisoft\Db\Tests\Common\CommonQueryBuilderTest; use function str_contains; @@ -659,4 +660,48 @@ public function testSelectScalar(array|bool|float|int|string $columns, string $e { parent::testSelectScalar($columns, $expected); } + + public function testJsonOverlapsConditionBuilder(): void + { + $db = $this->getConnection(); + $qb = $db->getQueryBuilder(); + + $params = []; + $sql = $qb->buildExpression(new JsonOverlapsCondition('column', [1, 2, 3]), $params); + + $this->assertSame('JSON_OVERLAPS(`column`, :qp0)', $sql); + $this->assertSame([':qp0' => '[1,2,3]'], $params); + + $db->close(); + } + + /** @dataProvider \Yiisoft\Db\Mysql\Tests\Provider\QueryBuilderProvider::overlapsCondition */ + public function testOverlapsCondition(iterable|ExpressionInterface $values, int $expectedCount): void + { + $db = $this->getConnection(); + + $count = (new Query($db)) + ->from('json_type') + ->where(new JsonOverlapsCondition('json_col', $values)) + ->count(); + + $this->assertSame($expectedCount, $count); + + $db->close(); + } + + /** @dataProvider \Yiisoft\Db\Mysql\Tests\Provider\QueryBuilderProvider::overlapsCondition */ + public function testOverlapsConditionOperator(iterable|ExpressionInterface $values, int $expectedCount): void + { + $db = $this->getConnection(); + + $count = (new Query($db)) + ->from('json_type') + ->where(['json overlaps', 'json_col', $values]) + ->count(); + + $this->assertSame($expectedCount, $count); + + $db->close(); + } } diff --git a/tests/Support/Fixture/mysql.sql b/tests/Support/Fixture/mysql.sql index f18170a61..77ca45fe7 100644 --- a/tests/Support/Fixture/mysql.sql +++ b/tests/Support/Fixture/mysql.sql @@ -36,6 +36,7 @@ DROP TABLE IF EXISTS `T_constraints_2` CASCADE; DROP TABLE IF EXISTS `T_constraints_1` CASCADE; DROP TABLE IF EXISTS `T_upsert` CASCADE; DROP TABLE IF EXISTS `T_upsert_1`; +DROP TABLE IF EXISTS `json_type` CASCADE; CREATE TABLE `constraints` ( @@ -251,6 +252,12 @@ CREATE TABLE `beta` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `json_type` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `json_col` JSON, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + CREATE VIEW `animal_view` AS SELECT * FROM `animal`; INSERT INTO `animal` (`type`) VALUES ('yiiunit\data\ar\Cat'); @@ -325,6 +332,11 @@ INSERT INTO `beta` (id, alpha_string_identifier) VALUES (6, '2b'); INSERT INTO `beta` (id, alpha_string_identifier) VALUES (7, '2b'); INSERT INTO `beta` (id, alpha_string_identifier) VALUES (8, '02'); +INSERT INTO `json_type` (json_col) VALUES (null); +INSERT INTO `json_type` (json_col) VALUES ('[]'); +INSERT INTO `json_type` (json_col) VALUES ('[1,2,3,null]'); +INSERT INTO `json_type` (json_col) VALUES ('[3,4,5]'); + /* bit test, see https://github.com/yiisoft/yii2/issues/9006 */ DROP TABLE IF EXISTS `bit_values` CASCADE; diff --git a/tests/Support/TestTrait.php b/tests/Support/TestTrait.php index 8f84925cf..483b57f0d 100644 --- a/tests/Support/TestTrait.php +++ b/tests/Support/TestTrait.php @@ -50,4 +50,13 @@ protected function setDsn(string $dsn): void { $this->dsn = $dsn; } + + public static function setUpBeforeClass(): void + { + $db = self::getDb(); + + DbHelper::loadFixture($db, __DIR__ . '/Fixture/mysql.sql'); + + $db->close(); + } } From 21e6a957aad3d686c5739efbc3c8cd68ed95c139 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sat, 29 Jun 2024 15:10:35 +0700 Subject: [PATCH 2/7] Skip tests for MariaDB < 10.9 --- tests/QueryBuilderTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index dce599656..148509d80 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -664,6 +664,11 @@ public function testSelectScalar(array|bool|float|int|string $columns, string $e public function testJsonOverlapsConditionBuilder(): void { $db = $this->getConnection(); + + if (str_contains($db->getServerVersion(), 'MariaDB') && version_compare($db->getServerVersion(), '10.9', '<')) { + self::markTestSkipped('MariaDB < 10.9 does not support JSON_OVERLAPS function.'); + } + $qb = $db->getQueryBuilder(); $params = []; @@ -680,6 +685,10 @@ public function testOverlapsCondition(iterable|ExpressionInterface $values, int { $db = $this->getConnection(); + if (str_contains($db->getServerVersion(), 'MariaDB') && version_compare($db->getServerVersion(), '10.9', '<')) { + self::markTestSkipped('MariaDB < 10.9 does not support JSON_OVERLAPS function.'); + } + $count = (new Query($db)) ->from('json_type') ->where(new JsonOverlapsCondition('json_col', $values)) @@ -695,6 +704,10 @@ public function testOverlapsConditionOperator(iterable|ExpressionInterface $valu { $db = $this->getConnection(); + if (str_contains($db->getServerVersion(), 'MariaDB') && version_compare($db->getServerVersion(), '10.9', '<')) { + self::markTestSkipped('MariaDB < 10.9 does not support JSON_OVERLAPS function.'); + } + $count = (new Query($db)) ->from('json_type') ->where(['json overlaps', 'json_col', $values]) From 0740f7716cb1729100993e265134b0d0edbc601a Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sat, 29 Jun 2024 15:15:12 +0700 Subject: [PATCH 3/7] Skip tests for MySQL < 8 --- tests/QueryBuilderTest.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index 148509d80..92cfd67f0 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -666,7 +666,9 @@ public function testJsonOverlapsConditionBuilder(): void $db = $this->getConnection(); if (str_contains($db->getServerVersion(), 'MariaDB') && version_compare($db->getServerVersion(), '10.9', '<')) { - self::markTestSkipped('MariaDB < 10.9 does not support JSON_OVERLAPS function.'); + self::markTestSkipped('MariaDB < 10.9 does not support JSON_OVERLAPS() function.'); + } elseif (version_compare($db->getServerVersion(), '8', '<')) { + self::markTestSkipped('MySQL < 8 does not support JSON_OVERLAPS() function.'); } $qb = $db->getQueryBuilder(); @@ -686,7 +688,9 @@ public function testOverlapsCondition(iterable|ExpressionInterface $values, int $db = $this->getConnection(); if (str_contains($db->getServerVersion(), 'MariaDB') && version_compare($db->getServerVersion(), '10.9', '<')) { - self::markTestSkipped('MariaDB < 10.9 does not support JSON_OVERLAPS function.'); + self::markTestSkipped('MariaDB < 10.9 does not support JSON_OVERLAPS() function.'); + } elseif (version_compare($db->getServerVersion(), '8', '<')) { + self::markTestSkipped('MySQL < 8 does not support JSON_OVERLAPS() function.'); } $count = (new Query($db)) @@ -705,7 +709,9 @@ public function testOverlapsConditionOperator(iterable|ExpressionInterface $valu $db = $this->getConnection(); if (str_contains($db->getServerVersion(), 'MariaDB') && version_compare($db->getServerVersion(), '10.9', '<')) { - self::markTestSkipped('MariaDB < 10.9 does not support JSON_OVERLAPS function.'); + self::markTestSkipped('MariaDB < 10.9 does not support JSON_OVERLAPS() function.'); + } elseif (version_compare($db->getServerVersion(), '8', '<')) { + self::markTestSkipped('MySQL < 8 does not support JSON_OVERLAPS() function.'); } $count = (new Query($db)) From ea22aa90e3349308562ffb12ba7428da805b74c2 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sat, 29 Jun 2024 16:40:59 +0700 Subject: [PATCH 4/7] Rename tests --- tests/QueryBuilderTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index 92cfd67f0..a3d97dc74 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -683,7 +683,7 @@ public function testJsonOverlapsConditionBuilder(): void } /** @dataProvider \Yiisoft\Db\Mysql\Tests\Provider\QueryBuilderProvider::overlapsCondition */ - public function testOverlapsCondition(iterable|ExpressionInterface $values, int $expectedCount): void + public function testJsonOverlapsCondition(iterable|ExpressionInterface $values, int $expectedCount): void { $db = $this->getConnection(); @@ -704,7 +704,7 @@ public function testOverlapsCondition(iterable|ExpressionInterface $values, int } /** @dataProvider \Yiisoft\Db\Mysql\Tests\Provider\QueryBuilderProvider::overlapsCondition */ - public function testOverlapsConditionOperator(iterable|ExpressionInterface $values, int $expectedCount): void + public function testJsonOverlapsConditionOperator(iterable|ExpressionInterface $values, int $expectedCount): void { $db = $this->getConnection(); From fe600bfc9fd41b381fee515ad08ec6407353199b Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sat, 29 Jun 2024 17:15:49 +0700 Subject: [PATCH 5/7] Add line to CHANGELOG.md [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a7abc8ae..53a994fd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Bug #320: Change visibility of `DDLQueryBuilder::getColumnDefinition()` method to `private` (@Tigrov) - Enh #321: Implement `SqlParser` and `ExpressionBuilder` driver classes (@Tigrov) - Chg #339: Replace call of `SchemaInterface::getRawTableName()` to `QuoterInterface::getRawTableName()` (@Tigrov) +- Enh #342: Add json overlaps condition builder (@Tigrov) ## 1.2.0 March 21, 2024 From a897572e39c3953bbd28935036ffc111b50cc39e Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sun, 30 Jun 2024 09:01:37 +0700 Subject: [PATCH 6/7] Test column as Expression --- tests/QueryBuilderTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index a3d97dc74..d15bca202 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -9,6 +9,7 @@ use Yiisoft\Db\Exception\InvalidArgumentException; use Yiisoft\Db\Exception\InvalidConfigException; use Yiisoft\Db\Exception\NotSupportedException; +use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Mysql\Tests\Support\TestTrait; use Yiisoft\Db\Query\Query; @@ -679,6 +680,13 @@ public function testJsonOverlapsConditionBuilder(): void $this->assertSame('JSON_OVERLAPS(`column`, :qp0)', $sql); $this->assertSame([':qp0' => '[1,2,3]'], $params); + // Test column as Expression + $params = []; + $sql = $qb->buildExpression(new JsonOverlapsCondition(new Expression('column'), [1, 2, 3]), $params); + + $this->assertSame('JSON_OVERLAPS(column, :qp0)', $sql); + $this->assertSame([':qp0' => '[1,2,3]'], $params); + $db->close(); } From 9d8fba3502d2861729c5adac43eabac726ba00a8 Mon Sep 17 00:00:00 2001 From: Sergei Tigrov Date: Mon, 1 Jul 2024 16:37:45 +0700 Subject: [PATCH 7/7] Apply suggestions from code review [skip ci] Co-authored-by: Alexander Makarov --- CHANGELOG.md | 2 +- src/Builder/JsonOverlapsConditionBuilder.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53a994fd6..0a5291a58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - Bug #320: Change visibility of `DDLQueryBuilder::getColumnDefinition()` method to `private` (@Tigrov) - Enh #321: Implement `SqlParser` and `ExpressionBuilder` driver classes (@Tigrov) - Chg #339: Replace call of `SchemaInterface::getRawTableName()` to `QuoterInterface::getRawTableName()` (@Tigrov) -- Enh #342: Add json overlaps condition builder (@Tigrov) +- Enh #342: Add JSON overlaps condition builder (@Tigrov) ## 1.2.0 March 21, 2024 diff --git a/src/Builder/JsonOverlapsConditionBuilder.php b/src/Builder/JsonOverlapsConditionBuilder.php index 5e8645766..bbe1baf22 100644 --- a/src/Builder/JsonOverlapsConditionBuilder.php +++ b/src/Builder/JsonOverlapsConditionBuilder.php @@ -21,7 +21,7 @@ final class JsonOverlapsConditionBuilder extends AbstractOverlapsConditionBuilde /** * Build SQL for {@see JsonOverlapsCondition}. * - * @param JsonOverlapsCondition $expression the {@see JsonOverlapsCondition} to be built. + * @param JsonOverlapsCondition $expression The {@see JsonOverlapsCondition} to be built. * * @throws Exception * @throws InvalidArgumentException