Skip to content

Commit

Permalink
Add json overlaps condition builder (#342)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov authored Jul 4, 2024
1 parent b177c8b commit df9510e
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
44 changes: 44 additions & 0 deletions src/Builder/JsonOverlapsConditionBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Mysql\Builder;

use Yiisoft\Db\Exception\Exception;
use Yiisoft\Db\Exception\InvalidArgumentException;
use Yiisoft\Db\Exception\InvalidConfigException;
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Expression\JsonExpression;
use Yiisoft\Db\QueryBuilder\Condition\Builder\AbstractOverlapsConditionBuilder;
use Yiisoft\Db\QueryBuilder\Condition\JsonOverlapsCondition;

/**
* Builds expressions for {@see JsonOverlapsCondition} for MySQL Server.
*/
final class JsonOverlapsConditionBuilder extends AbstractOverlapsConditionBuilder
{
/**
* Build SQL for {@see JsonOverlapsCondition}.
*
* @param JsonOverlapsCondition $expression The {@see JsonOverlapsCondition} to be built.
*
* @throws Exception
* @throws InvalidArgumentException
* @throws InvalidConfigException
* @throws NotSupportedException
*/
public function build(ExpressionInterface $expression, array &$params = []): string
{
$column = $this->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)";
}
}
3 changes: 3 additions & 0 deletions src/DQLQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -84,6 +86,7 @@ protected function defaultExpressionBuilders(): array
parent::defaultExpressionBuilders(),
[
JsonExpression::class => JsonExpressionBuilder::class,
JsonOverlapsCondition::class => JsonOverlapsConditionBuilder::class,
Expression::class => ExpressionBuilder::class,
]
);
Expand Down
72 changes: 72 additions & 0 deletions tests/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
}
12 changes: 12 additions & 0 deletions tests/Support/Fixture/mysql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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`
(
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions tests/Support/TestTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

0 comments on commit df9510e

Please sign in to comment.