diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a7abc8ae..0a5291a58 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 diff --git a/src/Builder/JsonOverlapsConditionBuilder.php b/src/Builder/JsonOverlapsConditionBuilder.php new file mode 100644 index 000000000..bbe1baf22 --- /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..d15bca202 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -9,10 +9,12 @@ 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; use Yiisoft\Db\Query\QueryInterface; +use Yiisoft\Db\QueryBuilder\Condition\JsonOverlapsCondition; use Yiisoft\Db\Tests\Common\CommonQueryBuilderTest; use function str_contains; @@ -659,4 +661,74 @@ public function testSelectScalar(array|bool|float|int|string $columns, string $e { parent::testSelectScalar($columns, $expected); } + + 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.'); + } elseif (version_compare($db->getServerVersion(), '8', '<')) { + self::markTestSkipped('MySQL < 8 does not support JSON_OVERLAPS() function.'); + } + + $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); + + // 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(); + } + + /** @dataProvider \Yiisoft\Db\Mysql\Tests\Provider\QueryBuilderProvider::overlapsCondition */ + public function testJsonOverlapsCondition(iterable|ExpressionInterface $values, int $expectedCount): 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.'); + } elseif (version_compare($db->getServerVersion(), '8', '<')) { + self::markTestSkipped('MySQL < 8 does not support JSON_OVERLAPS() function.'); + } + + $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 testJsonOverlapsConditionOperator(iterable|ExpressionInterface $values, int $expectedCount): 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.'); + } elseif (version_compare($db->getServerVersion(), '8', '<')) { + self::markTestSkipped('MySQL < 8 does not support JSON_OVERLAPS() function.'); + } + + $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(); + } }