Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
- Enh #1031: Optimize SQL generation for `Not` condition (@vjik)
- Chg #1037: Change result type of `QueryBuilderInterface::getExpressionBuilder()` and
`DQLQueryBuilderInterface::getExpressionBuilder()` methods to `ExpressionBuilderInterface` (@vjik)
- New #1029: Add functions as expressions (@Tigrov)

## 1.3.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 @@ -20,5 +20,6 @@
<RiskyTruthyFalsyComparison errorLevel="suppress" />
<MissingClassConstType errorLevel="suppress" />
<ClassMustBeFinal errorLevel="suppress" />
<MoreSpecificImplementedParamType errorLevel="suppress" />
</issueHandlers>
</psalm>
44 changes: 44 additions & 0 deletions src/Expression/Function/ArrayMerge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Expression\Function;

use Yiisoft\Db\Schema\Column\ColumnInterface;

/**
* Represents an SQL expression that returns the merged array from a list of operands.
*
* Example usage:
*
* ```php
* $arrayMerge = new ArrayMerge('operand1', 'operand2');
* ```
*
* For example, it will be generated into the following SQL expression in PostgreSQL:
*
* ```sql
* ARRAY(SELECT DISTINCT UNNEST(operand1 || operand2))
* ```
*/
final class ArrayMerge extends MultiOperandFunction
{
private string|ColumnInterface $type = '';

/**
* Returns the type of the operands. Empty string if not set.
*/
public function getType(): string|ColumnInterface
{
return $this->type;
}

/**
* Sets the type of the operands.
*/
public function type(string|ColumnInterface $type): self
{
$this->type = $type;
return $this;
}
}
37 changes: 37 additions & 0 deletions src/Expression/Function/Builder/GreatestBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Expression\Function\Builder;

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
{
$builtOperands = [];

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

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

declare(strict_types=1);

namespace Yiisoft\Db\Expression\Function\Builder;

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
{
$builtOperands = [];

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

return 'LEAST(' . implode(', ', $builtOperands) . ')';
}
}
43 changes: 43 additions & 0 deletions src/Expression/Function/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\Expression\Function\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 `LENGTH()` function expressions for {@see Length} objects.
*
* @implements ExpressionBuilderInterface<Length>
*/
final class LengthBuilder implements ExpressionBuilderInterface
{
public function __construct(private readonly QueryBuilderInterface $queryBuilder)
{
}

/**
* Builds a SQL `LENGTH()` 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 `LENGTH()` function expression.
*/
public function build(ExpressionInterface $expression, array &$params = []): string
{
$operand = $expression->operand;

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

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

declare(strict_types=1);

namespace Yiisoft\Db\Expression\Function\Builder;

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 value FROM (
* SELECT operand1 AS value
* UNION
* SELECT operand2 AS value
* ) AS t ORDER BY LENGTH(value) DESC LIMIT 1)
* ```
*
* @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 value FROM ($unions) AS t ORDER BY LENGTH(value) DESC LIMIT 1)";
}
}
78 changes: 78 additions & 0 deletions src/Expression/Function/Builder/MultiOperandFunctionBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Expression\Function\Builder;

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

use function count;
use function is_string;

/**
* Base class for building SQL representation of multi-operand function expressions.
*
* @template T as MultiOperandFunction
* @implements ExpressionBuilderInterface<T>
*/
abstract class MultiOperandFunctionBuilder implements ExpressionBuilderInterface
{
/**
* Builds a SQL multi-operand function expression from the given {@see MultiOperandFunction} instance.
*
* @param MultiOperandFunction $expression The expression to build from.
* @param array $params The parameters to be bound to the query.
*
* @psalm-param T $expression
*
* @return string SQL multi-operand function expression.
*/
abstract protected function buildFromExpression(MultiOperandFunction $expression, array &$params): string;

public function __construct(protected readonly QueryBuilderInterface $queryBuilder)
{
}

/**
* Builds a SQL multi-operand function expression from the given {@see MultiOperandFunction} instance.
*
* @param MultiOperandFunction $expression The expression to build.
* @param array $params The parameters to be bound to the query.
*
* @psalm-param T $expression
*
* @return string SQL multi-operand function expression.
*/
public function build(ExpressionInterface $expression, array &$params = []): string
{
$operands = $expression->getOperands();

if (empty($operands)) {
throw new InvalidArgumentException(
'The ' . $expression::class . ' expression must have at least one operand.'
);
}

if (count($operands) === 1) {
return '(' . $this->buildOperand($operands[0], $params) . ')';
}

return $this->buildFromExpression($expression, $params);
}

/**
* Builds an operand expression of the multi-operand function.
*/
protected function buildOperand(mixed $operand, array &$params): string
{
if (is_string($operand)) {
return $operand;
}

return $this->queryBuilder->buildValue($operand, $params);
}
}
45 changes: 45 additions & 0 deletions src/Expression/Function/Builder/ShortestBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Expression\Function\Builder;

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 value FROM (
* SELECT operand1 AS value
* UNION
* SELECT operand2 AS value
* ) AS t ORDER BY LENGTH(value) ASC LIMIT 1)
* ```
*
* @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 value FROM ($unions) AS t ORDER BY LENGTH(value) ASC LIMIT 1)";
}
}
26 changes: 26 additions & 0 deletions src/Expression/Function/Greatest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Expression\Function;

use Yiisoft\Db\Expression\Function\Builder\GreatestBuilder;

/**
* Represents a SQL `GREATEST()` function that returns the greatest value from a list of values or expressions.
*
* Example usage:
*
* ```php
* $greatest = new Greatest(1, 'a + b', $db->select('column')->from('table')->where(['id' => 1]));
* ```
*
* ```sql
* GREATEST(1, a + b, (SELECT "column" FROM "table" WHERE "id" = 1))
* ```
*
* @see GreatestBuilder for building SQL representations of this function expression.
*/
final class Greatest extends MultiOperandFunction
{
}
Loading
Loading