Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
- Enh #383: Refactor `TableSchema` and `Schema` classes (@Tigrov)
- Enh #386: Support column's collation (@Tigrov)
- New #391: Add `Connection::getColumnBuilderClass()` method (@Tigrov)
- New #390: Implement `ArrayMergeBuilder`, `GreatestBuilder`, `LeastBuilder`, `LengthBuilder`, `LongestBuilder`
and `ShortestBuilder` 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 @@ -19,5 +19,6 @@
<issueHandlers>
<MixedAssignment errorLevel="suppress" />
<RiskyTruthyFalsyComparison errorLevel="suppress" />
<MoreSpecificImplementedParamType errorLevel="suppress" />
</issueHandlers>
</psalm>
50 changes: 50 additions & 0 deletions src/Builder/ArrayMergeBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Mssql\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 '[' + STRING_AGG('"' + STRING_ESCAPE(value, 'json') + '"', ',') + ']' AS value FROM (
* SELECT value FROM OPENJSON(operand1)
* UNION
* SELECT value FROM OPENJSON(operand2)
* ) AS t)
* ```
*
* @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) {
$selects[] = 'SELECT value FROM OPENJSON(' . $this->buildOperand($operand, $params) . ')';
}

$unions = implode(' UNION ', $selects);

return <<<SQL
(SELECT '[' + STRING_AGG('"' + STRING_ESCAPE(value, 'json') + '"', ',') + ']' AS value FROM ($unions) AS t)
SQL;
}
}
52 changes: 52 additions & 0 deletions src/Builder/GreatestBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Mssql\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 `GREATEST()` function expressions for {@see Greatest} objects.
*
* @extends MultiOperandFunctionBuilder<Greatest>
*/
final class GreatestBuilder extends MultiOperandFunctionBuilder
{
/**
* Builds a SQL `GREATEST()` 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 `GREATEST()` function expression.
*/
protected function buildFromExpression(MultiOperandFunction $expression, array &$params): string
{
$serverVersion = $this->queryBuilder->getServerInfo()->getVersion();

if (version_compare($serverVersion, '16', '<')) {
$builtSelects = [];

foreach ($expression->getOperands() as $operand) {
$builtSelects[] = 'SELECT ' . $this->buildOperand($operand, $params) . ' AS value';
}

$unions = implode(' UNION ', $builtSelects);

return "(SELECT MAX(value) FROM ($unions) AS t)";
}

$builtOperands = [];

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

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

declare(strict_types=1);

namespace Yiisoft\Db\Mssql\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 `LEAST()` function expressions for {@see Least} objects.
*
* @extends MultiOperandFunctionBuilder<Least>
*/
final class LeastBuilder extends MultiOperandFunctionBuilder
{
/**
* Builds a SQL `LEAST()` 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 `LEAST()` function expression.
*/
protected function buildFromExpression(MultiOperandFunction $expression, array &$params): string
{
$serverVersion = $this->queryBuilder->getServerInfo()->getVersion();

if (version_compare($serverVersion, '16', '<')) {
$builtSelects = [];
foreach ($expression->getOperands() as $operand) {
$builtSelects[] = 'SELECT ' . $this->buildOperand($operand, $params) . ' AS value';
}

$unions = implode(' UNION ', $builtSelects);

return "(SELECT MIN(value) FROM ($unions) AS t)";
}

$builtOperands = [];

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

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

declare(strict_types=1);

namespace Yiisoft\Db\Mssql\Builder;

use Yiisoft\Db\Expression\Builder\ExpressionBuilderInterface;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Expression\Function\Length;
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;

use function is_string;

/**
* Builds SQL `LEN()` function expressions for {@see Length} objects.
*
* @implements ExpressionBuilderInterface<Length>
*/
final class LengthBuilder implements ExpressionBuilderInterface
{
public function __construct(private readonly QueryBuilderInterface $queryBuilder)
{
}

/**
* Builds a SQL `LEN()` function expression from the given {@see Length} object.
*
* @param Length $expression The expression to build.
* @param array $params The parameters to be bound to the query.
*
* @return string The SQL `LEN()` function expression.
*/
public function build(ExpressionInterface $expression, array &$params = []): string
{
$operand = $expression->operand;

if (is_string($operand)) {
return "LEN($operand)";
}

return 'LEN(' . $this->queryBuilder->buildExpression($operand, $params) . ')';
}
}
47 changes: 47 additions & 0 deletions src/Builder/LongestBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Mssql\Builder;

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

/**
* Builds SQL representation of function expressions which returns the longest string from a set of operands.
*
* ```SQL
* (SELECT TOP 1 value FROM (
* SELECT operand1 AS value
* UNION
* SELECT operand2 AS value
* ) AS t ORDER BY LEN(value) DESC)
* ```
*
* @extends MultiOperandFunctionBuilder<Longest>
*/
final class LongestBuilder extends MultiOperandFunctionBuilder
{
/**
* Builds a SQL expression to represent the function which returns the longest string.
*
* @param Greatest $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) {
$selects[] = 'SELECT ' . $this->buildOperand($operand, $params) . ' AS value';
}

$unions = implode(' UNION ', $selects);

return "(SELECT TOP 1 value FROM ($unions) AS t ORDER BY LEN(value) DESC)";
}
}
46 changes: 46 additions & 0 deletions src/Builder/ShortestBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Mssql\Builder;

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

/**
* Builds SQL representation of function expressions which return the shortest string from a set of operands.
*
* ```SQL
* (SELECT TOP 1 value FROM (
* SELECT operand1 AS value
* UNION
* SELECT operand2 AS value
* ) AS t ORDER BY LEN(value) ASC)
* ```
*
* @extends MultiOperandFunctionBuilder<Shortest>
*/
final class ShortestBuilder extends MultiOperandFunctionBuilder
{
/**
* Builds a SQL expression to represent the function which returns the shortest string.
*
* @param Shortest $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) {
$selects[] = 'SELECT ' . $this->buildOperand($operand, $params) . ' AS value';
}

$unions = implode(' UNION ', $selects);

return "(SELECT TOP 1 value FROM ($unions) AS t ORDER BY LEN(value) ASC)";
}
}
18 changes: 18 additions & 0 deletions src/DQLQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,20 @@
use Yiisoft\Db\Exception\Exception;
use InvalidArgumentException;
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\Expression\Function\Length;
use Yiisoft\Db\Expression\Function\Longest;
use Yiisoft\Db\Expression\Function\Shortest;
use Yiisoft\Db\Mssql\Builder\ArrayMergeBuilder;
use Yiisoft\Db\Mssql\Builder\GreatestBuilder;
use Yiisoft\Db\Mssql\Builder\InBuilder;
use Yiisoft\Db\Mssql\Builder\LeastBuilder;
use Yiisoft\Db\Mssql\Builder\LengthBuilder;
use Yiisoft\Db\Mssql\Builder\LikeBuilder;
use Yiisoft\Db\Mssql\Builder\LongestBuilder;
use Yiisoft\Db\Mssql\Builder\ShortestBuilder;
use Yiisoft\Db\Query\Query;
use Yiisoft\Db\QueryBuilder\AbstractDQLQueryBuilder;
use Yiisoft\Db\QueryBuilder\Condition\In;
Expand Down Expand Up @@ -52,6 +64,12 @@ protected function defaultExpressionBuilders(): array
NotIn::class => InBuilder::class,
Like::class => LikeBuilder::class,
NotLike::class => LikeBuilder::class,
Length::class => LengthBuilder::class,
ArrayMerge::class => ArrayMergeBuilder::class,
Greatest::class => GreatestBuilder::class,
Least::class => LeastBuilder::class,
Longest::class => LongestBuilder::class,
Shortest::class => ShortestBuilder::class,
];
}

Expand Down
Loading
Loading