Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
- Enh #377: Remove `TableSchema` class and refactor `Schema` class (@Tigrov)
- Enh #380: Support column's collation (@Tigrov)
- New #385: Add `Connection::getColumnBuilderClass()` method (@Tigrov)
- New #384: Implement `ArrayMergeBuilder`, `GreatestBuilder` and `LeastBuilder` classes (@Tigrov)

## 1.2.0 March 21, 2024

Expand Down
1 change: 1 addition & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
<issueHandlers>
<MixedAssignment errorLevel="suppress"/>
<RiskyTruthyFalsyComparison errorLevel="suppress" />
<MoreSpecificImplementedParamType errorLevel="suppress" />
</issueHandlers>
</psalm>
48 changes: 48 additions & 0 deletions src/Builder/ArrayMergeBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Sqlite\Builder;

use Yiisoft\Db\Expression\Function\ArrayMerge;
use Yiisoft\Db\Expression\Function\Builder\MultiOperandFunctionBuilder;
use Yiisoft\Db\Expression\Function\MultiOperandFunction;

use function implode;

/**
* Builds SQL expressions which merge arrays for {@see ArrayMerge} objects.
*
* ```sql
* (SELECT json_group_array(value) AS value FROM (
* SELECT value FROM json_each(operand1)
* UNION
* SELECT value FROM json_each(operand2)
* ))
* ```
*
* @extends MultiOperandFunctionBuilder<ArrayMerge>
*/
final class ArrayMergeBuilder extends MultiOperandFunctionBuilder
{
/**
* Builds a SQL expression which merges arrays from the given {@see ArrayMerge} object.
*
* @param ArrayMerge $expression The expression to build.
* @param array $params The parameters to bind.
*
* @return string The SQL expression.
*/
protected function buildFromExpression(MultiOperandFunction $expression, array &$params): string
{
$selects = [];

foreach ($expression->getOperands() as $operand) {
$builtOperand = $this->buildOperand($operand, $params);

$selects[] = "SELECT value FROM json_each($builtOperand)";
}

return '(SELECT json_group_array(value) AS value FROM (' . implode(' UNION ', $selects) . '))';
}
}
38 changes: 38 additions & 0 deletions src/Builder/GreatestBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Sqlite\Builder;

use Yiisoft\Db\Expression\Function\Builder\MultiOperandFunctionBuilder;
use Yiisoft\Db\Expression\Function\Greatest;
use Yiisoft\Db\Expression\Function\MultiOperandFunction;

use function implode;

/**
* Builds SQL `MAX()` function expressions for {@see Greatest} objects.
*
* @extends MultiOperandFunctionBuilder<Greatest>
*/
final class GreatestBuilder extends MultiOperandFunctionBuilder
{
/**
* Builds a SQL `MAX()` function expression from the given {@see Greatest} object.
*
* @param Greatest $expression The expression to build.
* @param array $params The parameters to bind.
*
* @return string The SQL `MAX()` function expression.
*/
protected function buildFromExpression(MultiOperandFunction $expression, array &$params): string
{
$builtOperands = [];

foreach ($expression->getOperands() as $operand) {
$builtOperands[] = $this->buildOperand($operand, $params);
}

return 'MAX(' . implode(', ', $builtOperands) . ')';
}
}
38 changes: 38 additions & 0 deletions src/Builder/LeastBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Sqlite\Builder;

use Yiisoft\Db\Expression\Function\Builder\MultiOperandFunctionBuilder;
use Yiisoft\Db\Expression\Function\Least;
use Yiisoft\Db\Expression\Function\MultiOperandFunction;

use function implode;

/**
* Builds SQL `MIN()` function expressions for {@see Least} objects.
*
* @extends MultiOperandFunctionBuilder<Least>
*/
final class LeastBuilder extends MultiOperandFunctionBuilder
{
/**
* Builds a SQL `MIN()` function expression from the given {@see Least} object.
*
* @param Least $expression The expression to build.
* @param array $params The parameters to bind.
*
* @return string The SQL `MIN()` function expression.
*/
protected function buildFromExpression(MultiOperandFunction $expression, array &$params): string
{
$builtOperands = [];

foreach ($expression->getOperands() as $operand) {
$builtOperands[] = $this->buildOperand($operand, $params);
}

return 'MIN(' . implode(', ', $builtOperands) . ')';
}
}
9 changes: 9 additions & 0 deletions src/DQLQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Expression\Function\ArrayMerge;
use Yiisoft\Db\Expression\Function\Greatest;
use Yiisoft\Db\Expression\Function\Least;
use Yiisoft\Db\Query\Query;
use Yiisoft\Db\Query\QueryInterface;
use Yiisoft\Db\QueryBuilder\AbstractDQLQueryBuilder;
Expand All @@ -14,8 +17,11 @@
use Yiisoft\Db\QueryBuilder\Condition\JsonOverlaps;
use Yiisoft\Db\QueryBuilder\Condition\NotIn;
use Yiisoft\Db\QueryBuilder\Condition\NotLike;
use Yiisoft\Db\Sqlite\Builder\ArrayMergeBuilder;
use Yiisoft\Db\Sqlite\Builder\GreatestBuilder;
use Yiisoft\Db\Sqlite\Builder\InBuilder;
use Yiisoft\Db\Sqlite\Builder\JsonOverlapsBuilder;
use Yiisoft\Db\Sqlite\Builder\LeastBuilder;
use Yiisoft\Db\Sqlite\Builder\LikeBuilder;

use function array_filter;
Expand Down Expand Up @@ -142,6 +148,9 @@ protected function defaultExpressionBuilders(): array
NotLike::class => LikeBuilder::class,
In::class => InBuilder::class,
NotIn::class => InBuilder::class,
ArrayMerge::class => ArrayMergeBuilder::class,
Greatest::class => GreatestBuilder::class,
Least::class => LeastBuilder::class,
];
}
}
53 changes: 53 additions & 0 deletions tests/Provider/QueryBuilderProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Yiisoft\Db\Constant\DataType;
use Yiisoft\Db\Constant\PseudoType;
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Expression\Function\ArrayMerge;
use Yiisoft\Db\Expression\Param;
use Yiisoft\Db\Query\Query;
use Yiisoft\Db\QueryBuilder\Condition\In;
Expand All @@ -16,6 +17,7 @@

use function array_replace;
use function array_splice;
use function strtr;

final class QueryBuilderProvider extends \Yiisoft\Db\Tests\Provider\QueryBuilderProvider
{
Expand Down Expand Up @@ -413,4 +415,55 @@ public static function prepareValue(): array

return $values;
}

public static function multiOperandFunctionClasses(): array
{
return [
...parent::multiOperandFunctionClasses(),
ArrayMerge::class => [ArrayMerge::class],
];
}

public static function multiOperandFunctionBuilder(): array
{
$data = parent::multiOperandFunctionBuilder();

$intQuery = self::getDb()->select(10);
$intQuerySql = '(SELECT 10)';
$stringParam = new Param('[3,4,5]', DataType::STRING);

foreach ($data as &$value) {
$value[2] = strtr($value[2], [
'GREATEST(' => 'MAX(',
'LEAST(' => 'MIN(',
]);
}

return [
...$data,
'ArrayMerge with 1 operand' => [
ArrayMerge::class,
["'[1,2,3]'"],
"('[1,2,3]')",
[1, 2, 3],
],
'ArrayMerge with 2 operands' => [
ArrayMerge::class,
["'[1,2,3]'", $stringParam],
"(SELECT json_group_array(value) AS value FROM (SELECT value FROM json_each('[1,2,3]') UNION SELECT value FROM json_each(:qp0)))",
[1, 2, 3, 4, 5],
[':qp0' => $stringParam],
],
'ArrayMerge with 4 operands' => [
ArrayMerge::class,
["'[1,2,3]'", [5,6,7], $stringParam, $intQuery],
"(SELECT json_group_array(value) AS value FROM (SELECT value FROM json_each('[1,2,3]') UNION SELECT value FROM json_each(:qp0) UNION SELECT value FROM json_each(:qp1) UNION SELECT value FROM json_each($intQuerySql)))",
[1, 2, 3, 4, 5, 6, 7, 10],
[
':qp0' => new Param('[5,6,7]', DataType::STRING),
':qp1' => $stringParam,
],
],
];
}
}
27 changes: 27 additions & 0 deletions tests/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -719,4 +719,31 @@ public function testCaseExpressionBuilder(
): void {
parent::testCaseExpressionBuilder($case, $expectedSql, $expectedParams, $expectedResult);
}

#[DataProviderExternal(QueryBuilderProvider::class, 'lengthBuilder')]
public function testLengthBuilder(
string|ExpressionInterface $operand,
string $expectedSql,
int $expectedResult,
array $expectedParams = [],
): void {
parent::testLengthBuilder($operand, $expectedSql, $expectedResult, $expectedParams);
}

#[DataProviderExternal(QueryBuilderProvider::class, 'multiOperandFunctionBuilder')]
public function testMultiOperandFunctionBuilder(
string $class,
array $operands,
string $expectedSql,
array|string|int $expectedResult,
array $expectedParams = [],
): void {
parent::testMultiOperandFunctionBuilder($class, $operands, $expectedSql, $expectedResult, $expectedParams);
}

#[DataProviderExternal(QueryBuilderProvider::class, 'multiOperandFunctionClasses')]
public function testMultiOperandFunctionBuilderWithoutOperands(string $class): void
{
parent::testMultiOperandFunctionBuilderWithoutOperands($class);
}
}
Loading