Skip to content

Commit

Permalink
Add json overlaps condition builders
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov committed Jun 29, 2024
1 parent a31bdf6 commit 35a82f6
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 0 deletions.
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\Sqlite\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 SQLite 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 "EXISTS(SELECT value FROM json_each($column) INTERSECT SELECT value FROM json_each($values))=1";
}
}
3 changes: 3 additions & 0 deletions src/DQLQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
use Yiisoft\Db\Query\QueryInterface;
use Yiisoft\Db\QueryBuilder\AbstractDQLQueryBuilder;
use Yiisoft\Db\QueryBuilder\Condition\InCondition;
use Yiisoft\Db\QueryBuilder\Condition\JsonOverlapsCondition;
use Yiisoft\Db\QueryBuilder\Condition\LikeCondition;
use Yiisoft\Db\Sqlite\Builder\ExpressionBuilder;
use Yiisoft\Db\Sqlite\Builder\InConditionBuilder;
use Yiisoft\Db\Sqlite\Builder\JsonExpressionBuilder;
use Yiisoft\Db\Sqlite\Builder\JsonOverlapsConditionBuilder;
use Yiisoft\Db\Sqlite\Builder\LikeConditionBuilder;

use function array_filter;
Expand Down Expand Up @@ -135,6 +137,7 @@ public function buildUnion(array $unions, array &$params = []): string
protected function defaultExpressionBuilders(): array
{
return array_merge(parent::defaultExpressionBuilders(), [
JsonOverlapsCondition::class => JsonOverlapsConditionBuilder::class,
LikeCondition::class => LikeConditionBuilder::class,
InCondition::class => InConditionBuilder::class,
JsonExpression::class => JsonExpressionBuilder::class,
Expand Down
48 changes: 48 additions & 0 deletions tests/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Yiisoft\Db\Expression\JsonExpression;
use Yiisoft\Db\Query\Query;
use Yiisoft\Db\Query\QueryInterface;
use Yiisoft\Db\QueryBuilder\Condition\JsonOverlapsCondition;
use Yiisoft\Db\Schema\SchemaInterface;
use Yiisoft\Db\Sqlite\Column;
use Yiisoft\Db\Sqlite\Tests\Support\TestTrait;
Expand Down Expand Up @@ -776,4 +777,51 @@ 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(
'EXISTS(SELECT value FROM json_each(`column`) INTERSECT SELECT value FROM json_each(:qp0))=1',
$sql
);
$this->assertSame([':qp0' => '[1,2,3]'], $params);

$db->close();
}

/** @dataProvider \Yiisoft\Db\Sqlite\Tests\Provider\QueryBuilderProvider::overlapsCondition */
public function testOverlapsCondition(iterable|ExpressionInterface $values, int $expectedCount): void
{
$db = $this->getConnection(true);

$count = (new Query($db))
->from('json_type')
->where(new JsonOverlapsCondition('json_col', $values))
->count();

$this->assertSame($expectedCount, $count);

$db->close();
}

/** @dataProvider \Yiisoft\Db\Sqlite\Tests\Provider\QueryBuilderProvider::overlapsCondition */
public function testOverlapsConditionOperator(iterable|ExpressionInterface $values, int $expectedCount): void
{
$db = $this->getConnection(true);

$count = (new Query($db))
->from('json_type')
->where(['json overlaps', 'json_col', $values])
->count();

$this->assertSame($expectedCount, $count);

$db->close();
}
}
13 changes: 13 additions & 0 deletions tests/Support/Fixture/sqlite.sql
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ DROP TABLE IF EXISTS "negative_default_values";
DROP TABLE IF EXISTS "animal";
DROP TABLE IF EXISTS "default_pk";
DROP TABLE IF EXISTS "notauto_pk";
DROP TABLE IF EXISTS "timestamp_default";
DROP TABLE IF EXISTS "json_type";
DROP VIEW IF EXISTS "animal_view";
DROP TABLE IF EXISTS "T_constraints_4";
DROP TABLE IF EXISTS "T_constraints_3";
Expand All @@ -25,6 +27,7 @@ DROP TABLE IF EXISTS "T_upsert_1";
DROP TABLE IF EXISTS "T_constraints_check";
DROP TABLE IF EXISTS "foreign_keys_parent";
DROP TABLE IF EXISTS "foreign_keys_child";
DROP TABLE IF EXISTS "json_type";

CREATE TABLE "profile" (
id INTEGER NOT NULL,
Expand Down Expand Up @@ -173,6 +176,11 @@ CREATE TABLE "timestamp_default" (
timestamp_text TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
); -- STRICT

CREATE TABLE "json_type" (
id INTEGER PRIMARY KEY,
json_col JSON
);

CREATE VIEW "animal_view" AS SELECT * FROM "animal";

INSERT INTO "animal" ("type") VALUES ('yiiunit\data\ar\Cat');
Expand Down Expand Up @@ -216,6 +224,11 @@ INSERT INTO "order_item_with_null_fk" (order_id, item_id, quantity, subtotal) VA
INSERT INTO "order_item_with_null_fk" (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0);
INSERT INTO "order_item_with_null_fk" (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0);

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";
Expand Down

0 comments on commit 35a82f6

Please sign in to comment.