Skip to content

Commit df3fa9a

Browse files
authored
Implement ArrayMergeBuilder, LengthBuilder, LongestBuilder and ShortestBuilder (#390)
1 parent a803f57 commit df3fa9a

11 files changed

+414
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
- Enh #383: Refactor `TableSchema` and `Schema` classes (@Tigrov)
5555
- Enh #386: Support column's collation (@Tigrov)
5656
- New #391: Add `Connection::getColumnBuilderClass()` method (@Tigrov)
57+
- New #390: Implement `ArrayMergeBuilder`, `GreatestBuilder`, `LeastBuilder`, `LengthBuilder`, `LongestBuilder`
58+
and `ShortestBuilder` classes (@Tigrov)
5759

5860
## 1.2.0 March 21, 2024
5961

psalm.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@
1919
<issueHandlers>
2020
<MixedAssignment errorLevel="suppress" />
2121
<RiskyTruthyFalsyComparison errorLevel="suppress" />
22+
<MoreSpecificImplementedParamType errorLevel="suppress" />
2223
</issueHandlers>
2324
</psalm>

src/Builder/ArrayMergeBuilder.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Db\Mssql\Builder;
6+
7+
use Yiisoft\Db\Expression\Function\ArrayMerge;
8+
use Yiisoft\Db\Expression\Function\Builder\MultiOperandFunctionBuilder;
9+
use Yiisoft\Db\Expression\Function\MultiOperandFunction;
10+
11+
use function implode;
12+
13+
/**
14+
* Builds SQL expressions which merge arrays for {@see ArrayMerge} objects.
15+
*
16+
* ```sql
17+
* (SELECT '[' + STRING_AGG('"' + STRING_ESCAPE(value, 'json') + '"', ',') + ']' AS value FROM (
18+
* SELECT value FROM OPENJSON(operand1)
19+
* UNION
20+
* SELECT value FROM OPENJSON(operand2)
21+
* ) AS t)
22+
* ```
23+
*
24+
* @extends MultiOperandFunctionBuilder<ArrayMerge>
25+
*/
26+
final class ArrayMergeBuilder extends MultiOperandFunctionBuilder
27+
{
28+
/**
29+
* Builds a SQL expression which merges arrays from the given {@see ArrayMerge} object.
30+
*
31+
* @param ArrayMerge $expression The expression to build.
32+
* @param array $params The parameters to bind.
33+
*
34+
* @return string The SQL expression.
35+
*/
36+
protected function buildFromExpression(MultiOperandFunction $expression, array &$params): string
37+
{
38+
$selects = [];
39+
40+
foreach ($expression->getOperands() as $operand) {
41+
$selects[] = 'SELECT value FROM OPENJSON(' . $this->buildOperand($operand, $params) . ')';
42+
}
43+
44+
$unions = implode(' UNION ', $selects);
45+
46+
return <<<SQL
47+
(SELECT '[' + STRING_AGG('"' + STRING_ESCAPE(value, 'json') + '"', ',') + ']' AS value FROM ($unions) AS t)
48+
SQL;
49+
}
50+
}

src/Builder/GreatestBuilder.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Db\Mssql\Builder;
6+
7+
use Yiisoft\Db\Expression\Function\Builder\MultiOperandFunctionBuilder;
8+
use Yiisoft\Db\Expression\Function\Greatest;
9+
use Yiisoft\Db\Expression\Function\MultiOperandFunction;
10+
11+
use function implode;
12+
13+
/**
14+
* Builds SQL `GREATEST()` function expressions for {@see Greatest} objects.
15+
*
16+
* @extends MultiOperandFunctionBuilder<Greatest>
17+
*/
18+
final class GreatestBuilder extends MultiOperandFunctionBuilder
19+
{
20+
/**
21+
* Builds a SQL `GREATEST()` function expression from the given {@see Greatest} object.
22+
*
23+
* @param Greatest $expression The expression to build.
24+
* @param array $params The parameters to bind.
25+
*
26+
* @return string The SQL `GREATEST()` function expression.
27+
*/
28+
protected function buildFromExpression(MultiOperandFunction $expression, array &$params): string
29+
{
30+
$serverVersion = $this->queryBuilder->getServerInfo()->getVersion();
31+
32+
if (version_compare($serverVersion, '16', '<')) {
33+
$builtSelects = [];
34+
35+
foreach ($expression->getOperands() as $operand) {
36+
$builtSelects[] = 'SELECT ' . $this->buildOperand($operand, $params) . ' AS value';
37+
}
38+
39+
$unions = implode(' UNION ', $builtSelects);
40+
41+
return "(SELECT MAX(value) FROM ($unions) AS t)";
42+
}
43+
44+
$builtOperands = [];
45+
46+
foreach ($expression->getOperands() as $operand) {
47+
$builtOperands[] = $this->buildOperand($operand, $params);
48+
}
49+
50+
return 'GREATEST(' . implode(', ', $builtOperands) . ')';
51+
}
52+
}

src/Builder/LeastBuilder.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Db\Mssql\Builder;
6+
7+
use Yiisoft\Db\Expression\Function\Builder\MultiOperandFunctionBuilder;
8+
use Yiisoft\Db\Expression\Function\Least;
9+
use Yiisoft\Db\Expression\Function\MultiOperandFunction;
10+
11+
use function implode;
12+
13+
/**
14+
* Builds SQL `LEAST()` function expressions for {@see Least} objects.
15+
*
16+
* @extends MultiOperandFunctionBuilder<Least>
17+
*/
18+
final class LeastBuilder extends MultiOperandFunctionBuilder
19+
{
20+
/**
21+
* Builds a SQL `LEAST()` function expression from the given {@see Least} object.
22+
*
23+
* @param Least $expression The expression to build.
24+
* @param array $params The parameters to bind.
25+
*
26+
* @return string The SQL `LEAST()` function expression.
27+
*/
28+
protected function buildFromExpression(MultiOperandFunction $expression, array &$params): string
29+
{
30+
$serverVersion = $this->queryBuilder->getServerInfo()->getVersion();
31+
32+
if (version_compare($serverVersion, '16', '<')) {
33+
$builtSelects = [];
34+
foreach ($expression->getOperands() as $operand) {
35+
$builtSelects[] = 'SELECT ' . $this->buildOperand($operand, $params) . ' AS value';
36+
}
37+
38+
$unions = implode(' UNION ', $builtSelects);
39+
40+
return "(SELECT MIN(value) FROM ($unions) AS t)";
41+
}
42+
43+
$builtOperands = [];
44+
45+
foreach ($expression->getOperands() as $operand) {
46+
$builtOperands[] = $this->buildOperand($operand, $params);
47+
}
48+
49+
return 'LEAST(' . implode(', ', $builtOperands) . ')';
50+
}
51+
}

src/Builder/LengthBuilder.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Db\Mssql\Builder;
6+
7+
use Yiisoft\Db\Expression\Builder\ExpressionBuilderInterface;
8+
use Yiisoft\Db\Expression\ExpressionInterface;
9+
use Yiisoft\Db\Expression\Function\Length;
10+
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
11+
12+
use function is_string;
13+
14+
/**
15+
* Builds SQL `LEN()` function expressions for {@see Length} objects.
16+
*
17+
* @implements ExpressionBuilderInterface<Length>
18+
*/
19+
final class LengthBuilder implements ExpressionBuilderInterface
20+
{
21+
public function __construct(private readonly QueryBuilderInterface $queryBuilder)
22+
{
23+
}
24+
25+
/**
26+
* Builds a SQL `LEN()` function expression from the given {@see Length} object.
27+
*
28+
* @param Length $expression The expression to build.
29+
* @param array $params The parameters to be bound to the query.
30+
*
31+
* @return string The SQL `LEN()` function expression.
32+
*/
33+
public function build(ExpressionInterface $expression, array &$params = []): string
34+
{
35+
$operand = $expression->operand;
36+
37+
if (is_string($operand)) {
38+
return "LEN($operand)";
39+
}
40+
41+
return 'LEN(' . $this->queryBuilder->buildExpression($operand, $params) . ')';
42+
}
43+
}

src/Builder/LongestBuilder.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Db\Mssql\Builder;
6+
7+
use Yiisoft\Db\Expression\Function\Builder\MultiOperandFunctionBuilder;
8+
use Yiisoft\Db\Expression\Function\Greatest;
9+
use Yiisoft\Db\Expression\Function\Longest;
10+
use Yiisoft\Db\Expression\Function\MultiOperandFunction;
11+
12+
/**
13+
* Builds SQL representation of function expressions which returns the longest string from a set of operands.
14+
*
15+
* ```SQL
16+
* (SELECT TOP 1 value FROM (
17+
* SELECT operand1 AS value
18+
* UNION
19+
* SELECT operand2 AS value
20+
* ) AS t ORDER BY LEN(value) DESC)
21+
* ```
22+
*
23+
* @extends MultiOperandFunctionBuilder<Longest>
24+
*/
25+
final class LongestBuilder extends MultiOperandFunctionBuilder
26+
{
27+
/**
28+
* Builds a SQL expression to represent the function which returns the longest string.
29+
*
30+
* @param Greatest $expression The expression to build.
31+
* @param array $params The parameters to bind.
32+
*
33+
* @return string The SQL expression.
34+
*/
35+
protected function buildFromExpression(MultiOperandFunction $expression, array &$params): string
36+
{
37+
$selects = [];
38+
39+
foreach ($expression->getOperands() as $operand) {
40+
$selects[] = 'SELECT ' . $this->buildOperand($operand, $params) . ' AS value';
41+
}
42+
43+
$unions = implode(' UNION ', $selects);
44+
45+
return "(SELECT TOP 1 value FROM ($unions) AS t ORDER BY LEN(value) DESC)";
46+
}
47+
}

src/Builder/ShortestBuilder.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Db\Mssql\Builder;
6+
7+
use Yiisoft\Db\Expression\Function\Builder\MultiOperandFunctionBuilder;
8+
use Yiisoft\Db\Expression\Function\MultiOperandFunction;
9+
use Yiisoft\Db\Expression\Function\Shortest;
10+
11+
/**
12+
* Builds SQL representation of function expressions which return the shortest string from a set of operands.
13+
*
14+
* ```SQL
15+
* (SELECT TOP 1 value FROM (
16+
* SELECT operand1 AS value
17+
* UNION
18+
* SELECT operand2 AS value
19+
* ) AS t ORDER BY LEN(value) ASC)
20+
* ```
21+
*
22+
* @extends MultiOperandFunctionBuilder<Shortest>
23+
*/
24+
final class ShortestBuilder extends MultiOperandFunctionBuilder
25+
{
26+
/**
27+
* Builds a SQL expression to represent the function which returns the shortest string.
28+
*
29+
* @param Shortest $expression The expression to build.
30+
* @param array $params The parameters to bind.
31+
*
32+
* @return string The SQL expression.
33+
*/
34+
protected function buildFromExpression(MultiOperandFunction $expression, array &$params): string
35+
{
36+
$selects = [];
37+
38+
foreach ($expression->getOperands() as $operand) {
39+
$selects[] = 'SELECT ' . $this->buildOperand($operand, $params) . ' AS value';
40+
}
41+
42+
$unions = implode(' UNION ', $selects);
43+
44+
return "(SELECT TOP 1 value FROM ($unions) AS t ORDER BY LEN(value) ASC)";
45+
}
46+
}

src/DQLQueryBuilder.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,20 @@
77
use Yiisoft\Db\Exception\Exception;
88
use InvalidArgumentException;
99
use Yiisoft\Db\Expression\ExpressionInterface;
10+
use Yiisoft\Db\Expression\Function\ArrayMerge;
11+
use Yiisoft\Db\Expression\Function\Greatest;
12+
use Yiisoft\Db\Expression\Function\Least;
13+
use Yiisoft\Db\Expression\Function\Length;
14+
use Yiisoft\Db\Expression\Function\Longest;
15+
use Yiisoft\Db\Expression\Function\Shortest;
16+
use Yiisoft\Db\Mssql\Builder\ArrayMergeBuilder;
17+
use Yiisoft\Db\Mssql\Builder\GreatestBuilder;
1018
use Yiisoft\Db\Mssql\Builder\InBuilder;
19+
use Yiisoft\Db\Mssql\Builder\LeastBuilder;
20+
use Yiisoft\Db\Mssql\Builder\LengthBuilder;
1121
use Yiisoft\Db\Mssql\Builder\LikeBuilder;
22+
use Yiisoft\Db\Mssql\Builder\LongestBuilder;
23+
use Yiisoft\Db\Mssql\Builder\ShortestBuilder;
1224
use Yiisoft\Db\Query\Query;
1325
use Yiisoft\Db\QueryBuilder\AbstractDQLQueryBuilder;
1426
use Yiisoft\Db\QueryBuilder\Condition\In;
@@ -52,6 +64,12 @@ protected function defaultExpressionBuilders(): array
5264
NotIn::class => InBuilder::class,
5365
Like::class => LikeBuilder::class,
5466
NotLike::class => LikeBuilder::class,
67+
Length::class => LengthBuilder::class,
68+
ArrayMerge::class => ArrayMergeBuilder::class,
69+
Greatest::class => GreatestBuilder::class,
70+
Least::class => LeastBuilder::class,
71+
Longest::class => LongestBuilder::class,
72+
Shortest::class => ShortestBuilder::class,
5573
];
5674
}
5775

0 commit comments

Comments
 (0)