diff --git a/CHANGELOG.md b/CHANGELOG.md
index c2aa849de..bba33df6d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/psalm.xml b/psalm.xml
index 69d8c9739..990cf08c2 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -20,5 +20,6 @@
+
diff --git a/src/Expression/Function/ArrayMerge.php b/src/Expression/Function/ArrayMerge.php
new file mode 100644
index 000000000..e68690791
--- /dev/null
+++ b/src/Expression/Function/ArrayMerge.php
@@ -0,0 +1,44 @@
+type;
+ }
+
+ /**
+ * Sets the type of the operands.
+ */
+ public function type(string|ColumnInterface $type): self
+ {
+ $this->type = $type;
+ return $this;
+ }
+}
diff --git a/src/Expression/Function/Builder/GreatestBuilder.php b/src/Expression/Function/Builder/GreatestBuilder.php
new file mode 100644
index 000000000..e895be2d4
--- /dev/null
+++ b/src/Expression/Function/Builder/GreatestBuilder.php
@@ -0,0 +1,37 @@
+
+ */
+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) . ')';
+ }
+}
diff --git a/src/Expression/Function/Builder/LeastBuilder.php b/src/Expression/Function/Builder/LeastBuilder.php
new file mode 100644
index 000000000..967be884b
--- /dev/null
+++ b/src/Expression/Function/Builder/LeastBuilder.php
@@ -0,0 +1,37 @@
+
+ */
+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) . ')';
+ }
+}
diff --git a/src/Expression/Function/Builder/LengthBuilder.php b/src/Expression/Function/Builder/LengthBuilder.php
new file mode 100644
index 000000000..b68301b6d
--- /dev/null
+++ b/src/Expression/Function/Builder/LengthBuilder.php
@@ -0,0 +1,43 @@
+
+ */
+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) . ')';
+ }
+}
diff --git a/src/Expression/Function/Builder/LongestBuilder.php b/src/Expression/Function/Builder/LongestBuilder.php
new file mode 100644
index 000000000..fe52e93f1
--- /dev/null
+++ b/src/Expression/Function/Builder/LongestBuilder.php
@@ -0,0 +1,46 @@
+
+ */
+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)";
+ }
+}
diff --git a/src/Expression/Function/Builder/MultiOperandFunctionBuilder.php b/src/Expression/Function/Builder/MultiOperandFunctionBuilder.php
new file mode 100644
index 000000000..5ca31bfb4
--- /dev/null
+++ b/src/Expression/Function/Builder/MultiOperandFunctionBuilder.php
@@ -0,0 +1,78 @@
+
+ */
+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);
+ }
+}
diff --git a/src/Expression/Function/Builder/ShortestBuilder.php b/src/Expression/Function/Builder/ShortestBuilder.php
new file mode 100644
index 000000000..a7aa1ad33
--- /dev/null
+++ b/src/Expression/Function/Builder/ShortestBuilder.php
@@ -0,0 +1,45 @@
+
+ */
+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)";
+ }
+}
diff --git a/src/Expression/Function/Greatest.php b/src/Expression/Function/Greatest.php
new file mode 100644
index 000000000..dc9cc9caf
--- /dev/null
+++ b/src/Expression/Function/Greatest.php
@@ -0,0 +1,26 @@
+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
+{
+}
diff --git a/src/Expression/Function/Least.php b/src/Expression/Function/Least.php
new file mode 100644
index 000000000..5fb1bac17
--- /dev/null
+++ b/src/Expression/Function/Least.php
@@ -0,0 +1,26 @@
+select('column')->from('table')->where(['id' => 1]));
+ * ```
+ *
+ * ```sql
+ * LEAST(1, a + b, (SELECT "column" FROM "table" WHERE "id" = 1))
+ * ```
+ *
+ * @see LeastBuilder for building SQL representations of this function expression.
+ */
+final class Least extends MultiOperandFunction
+{
+}
diff --git a/src/Expression/Function/Length.php b/src/Expression/Function/Length.php
new file mode 100644
index 000000000..bdf178aaa
--- /dev/null
+++ b/src/Expression/Function/Length.php
@@ -0,0 +1,34 @@
+operands = $operands;
+ }
+
+ public function add(mixed $operand): static
+ {
+ $this->operands[] = $operand;
+ return $this;
+ }
+
+ /**
+ * @return array List of operands.
+ */
+ public function getOperands(): array
+ {
+ return $this->operands;
+ }
+}
diff --git a/src/Expression/Function/Shortest.php b/src/Expression/Function/Shortest.php
new file mode 100644
index 000000000..9ad331124
--- /dev/null
+++ b/src/Expression/Function/Shortest.php
@@ -0,0 +1,25 @@
+ CaseExpressionBuilder::class,
ColumnName::class => ColumnNameBuilder::class,
Value::class => ValueBuilder::class,
+ Length::class => LengthBuilder::class,
+ Greatest::class => GreatestBuilder::class,
+ Least::class => LeastBuilder::class,
+ Longest::class => LongestBuilder::class,
+ Shortest::class => ShortestBuilder::class,
];
}
diff --git a/src/Schema/Column/AbstractArrayColumn.php b/src/Schema/Column/AbstractArrayColumn.php
index bd2b2339d..c2c82c58e 100644
--- a/src/Schema/Column/AbstractArrayColumn.php
+++ b/src/Schema/Column/AbstractArrayColumn.php
@@ -73,7 +73,6 @@ public function getDimension(): int
/**
* @param iterable|LazyArrayInterface|QueryInterface|string|null $value
- * @psalm-suppress MoreSpecificImplementedParamType
*/
public function dbTypecast(mixed $value): ExpressionInterface|null
{
diff --git a/src/Schema/Column/AbstractStructuredColumn.php b/src/Schema/Column/AbstractStructuredColumn.php
index 12270a607..323c6624e 100644
--- a/src/Schema/Column/AbstractStructuredColumn.php
+++ b/src/Schema/Column/AbstractStructuredColumn.php
@@ -50,7 +50,6 @@ public function getColumns(): array
/**
* @param array|object|string|null $value
- * @psalm-suppress MoreSpecificImplementedParamType
*/
public function dbTypecast(mixed $value): ExpressionInterface|null
{
diff --git a/src/Schema/Column/ArrayColumn.php b/src/Schema/Column/ArrayColumn.php
index 1bed802c5..a62ec8ec9 100644
--- a/src/Schema/Column/ArrayColumn.php
+++ b/src/Schema/Column/ArrayColumn.php
@@ -17,7 +17,6 @@ final class ArrayColumn extends AbstractArrayColumn
{
/**
* @param string|null $value The string retrieved value from the database that can be parsed into an array.
- * @psalm-suppress MoreSpecificImplementedParamType
*/
public function phpTypecast(mixed $value): array|null
{
diff --git a/src/Schema/Column/ArrayLazyColumn.php b/src/Schema/Column/ArrayLazyColumn.php
index 1c138cc5e..ccbd6dedd 100644
--- a/src/Schema/Column/ArrayLazyColumn.php
+++ b/src/Schema/Column/ArrayLazyColumn.php
@@ -17,7 +17,6 @@ final class ArrayLazyColumn extends AbstractArrayColumn
{
/**
* @param string|null $value The string retrieved value from the database that can be parsed into an array.
- * @psalm-suppress MoreSpecificImplementedParamType
*/
public function phpTypecast(mixed $value): LazyArray|null
{
diff --git a/src/Schema/Column/DateTimeColumn.php b/src/Schema/Column/DateTimeColumn.php
index 3b07c7628..cd95cfa1d 100644
--- a/src/Schema/Column/DateTimeColumn.php
+++ b/src/Schema/Column/DateTimeColumn.php
@@ -116,7 +116,6 @@ public function dbTypecast(mixed $value): string|ExpressionInterface|null
* If the database type does not have time zone information, the time zone will be set to the current PHP time zone.
*
* @param string|null $value
- * @psalm-suppress MoreSpecificImplementedParamType
*/
public function phpTypecast(mixed $value): DateTimeImmutable|null
{
diff --git a/src/Schema/Column/StructuredColumn.php b/src/Schema/Column/StructuredColumn.php
index 028c3667d..992ce6499 100644
--- a/src/Schema/Column/StructuredColumn.php
+++ b/src/Schema/Column/StructuredColumn.php
@@ -17,7 +17,6 @@ final class StructuredColumn extends AbstractStructuredColumn
{
/**
* @param string|null $value The string retrieved value from the database that can be parsed into an array.
- * @psalm-suppress MoreSpecificImplementedParamType
*/
public function phpTypecast(mixed $value): array|null
{
diff --git a/src/Schema/Column/StructuredLazyColumn.php b/src/Schema/Column/StructuredLazyColumn.php
index 3d2e0e3f8..263a58923 100644
--- a/src/Schema/Column/StructuredLazyColumn.php
+++ b/src/Schema/Column/StructuredLazyColumn.php
@@ -17,7 +17,6 @@ final class StructuredLazyColumn extends AbstractStructuredColumn
{
/**
* @param string|null $value The string retrieved value from the database that can be parsed into an array.
- * @psalm-suppress MoreSpecificImplementedParamType
*/
public function phpTypecast(mixed $value): StructuredLazyArray|null
{
diff --git a/tests/AbstractQueryBuilderTest.php b/tests/AbstractQueryBuilderTest.php
index 243ef4d84..3ba404c04 100644
--- a/tests/AbstractQueryBuilderTest.php
+++ b/tests/AbstractQueryBuilderTest.php
@@ -20,6 +20,7 @@
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Expression\Builder\ExpressionBuilderInterface;
use Yiisoft\Db\Expression\ExpressionInterface;
+use Yiisoft\Db\Expression\Function\Length;
use Yiisoft\Db\Query\Query;
use Yiisoft\Db\Query\QueryInterface;
use Yiisoft\Db\QueryBuilder\Condition\AndX;
@@ -2450,4 +2451,55 @@ public function testCaseExpressionBuilderEmpty(): void
$qb->buildExpression($case, $params);
}
+
+ #[DataProviderExternal(QueryBuilderProvider::class, 'lengthBuilder')]
+ public function testLengthBuilder(
+ string|ExpressionInterface $operand,
+ string $expectedSql,
+ int $expectedResult,
+ array $expectedParams = [],
+ ): void {
+ $db = $this->getConnection();
+ $qb = $db->getQueryBuilder();
+
+ $length = new Length($operand);
+ $params = [];
+
+ $this->assertSame($expectedSql, $qb->buildExpression($length, $params));
+ $this->assertSame($expectedParams, $params);
+ }
+
+ #[DataProviderExternal(QueryBuilderProvider::class, 'multiOperandFunctionBuilder')]
+ public function testMultiOperandFunctionBuilder(
+ string $class,
+ array $operands,
+ string $expectedSql,
+ array|string|int $expectedResult,
+ array $expectedParams = [],
+ ): void {
+ $db = $this->getConnection();
+ $qb = $db->getQueryBuilder();
+
+ $expression = new $class(...$operands);
+ $params = [];
+
+ $sql = $qb->buildExpression($expression, $params);
+
+ $this->assertSame($expectedSql, $sql);
+ Assert::arraysEquals($expectedParams, $params);
+ }
+
+ #[DataProviderExternal(QueryBuilderProvider::class, 'multiOperandFunctionClasses')]
+ public function testMultiOperandFunctionBuilderWithoutOperands(string $class): void
+ {
+ $db = $this->getConnection();
+ $qb = $db->getQueryBuilder();
+
+ $expression = new $class();
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage("The $class expression must have at least one operand.");
+
+ $qb->buildExpression($expression);
+ }
}
diff --git a/tests/Common/CommonQueryBuilderTest.php b/tests/Common/CommonQueryBuilderTest.php
index c4aefc42c..3ca4875bf 100644
--- a/tests/Common/CommonQueryBuilderTest.php
+++ b/tests/Common/CommonQueryBuilderTest.php
@@ -11,11 +11,18 @@
use Yiisoft\Db\Constant\DataType;
use Yiisoft\Db\Exception\Exception;
use Yiisoft\Db\Expression\CaseExpression;
+use Yiisoft\Db\Expression\ExpressionInterface;
+use Yiisoft\Db\Expression\Function\Length;
use Yiisoft\Db\Schema\Column\ColumnInterface;
use Yiisoft\Db\Tests\AbstractQueryBuilderTest;
use Yiisoft\Db\Tests\Provider\QueryBuilderProvider;
use Yiisoft\Db\Tests\Support\Assert;
+use function is_array;
+use function sort;
+
+use const SORT_NATURAL;
+
abstract class CommonQueryBuilderTest extends AbstractQueryBuilderTest
{
private function createTebleWithColumn(CommandInterface $command, string|ColumnInterface $column)
@@ -202,4 +209,45 @@ public function testCaseExpressionBuilder(
$db->close();
}
+
+ #[DataProviderExternal(QueryBuilderProvider::class, 'lengthBuilder')]
+ public function testLengthBuilder(
+ string|ExpressionInterface $operand,
+ string $expectedSql,
+ int $expectedResult,
+ array $expectedParams = [],
+ ): void {
+ parent::testLengthBuilder($operand, $expectedSql, $expectedResult, $expectedParams);
+
+ $db = $this->getConnection();
+
+ $length = new Length($operand);
+ $result = $db->select($length)->scalar();
+
+ $this->assertEquals($expectedResult, $result);
+ }
+
+ #[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);
+
+ $db = $this->getConnection();
+
+ $expression = new $class(...$operands);
+ $result = $db->select($expression)->scalar();
+
+ if (is_array($expectedResult)) {
+ $arrayCol = $db->getColumnBuilderClass()::array();
+ $result = $arrayCol->phpTypecast($result);
+ sort($result, SORT_NATURAL);
+ }
+
+ $this->assertEquals($expectedResult, $result);
+ }
}
diff --git a/tests/Db/Expression/Function/ArrayMergeTest.php b/tests/Db/Expression/Function/ArrayMergeTest.php
new file mode 100644
index 000000000..a799eb8a8
--- /dev/null
+++ b/tests/Db/Expression/Function/ArrayMergeTest.php
@@ -0,0 +1,28 @@
+assertSame('', $expression->getType());
+ $this->assertSame($expression, $expression->type('integer'));
+ $this->assertSame('integer', $expression->getType());
+
+ $intColumn = new IntegerColumn();
+ $this->assertSame($expression, $expression->type($intColumn));
+ $this->assertSame($intColumn, $expression->getType());
+ }
+}
diff --git a/tests/Db/Expression/Function/LengthTest.php b/tests/Db/Expression/Function/LengthTest.php
new file mode 100644
index 000000000..108cdab8b
--- /dev/null
+++ b/tests/Db/Expression/Function/LengthTest.php
@@ -0,0 +1,35 @@
+ ['expression'],
+ 'string' => [new Param('string', DataType::STRING)],
+ 'query' => [self::getDb()->select('column')->from('table')->where(['id' => 1])],
+ ];
+ }
+
+ #[DataProvider('dataOperands')]
+ public function testConstruct(string|ExpressionInterface $operand): void
+ {
+ $length = new Length($operand);
+
+ $this->assertSame($operand, $length->operand);
+ }
+}
diff --git a/tests/Db/Expression/Function/MultiOperandFunctionTest.php b/tests/Db/Expression/Function/MultiOperandFunctionTest.php
new file mode 100644
index 000000000..926cafa11
--- /dev/null
+++ b/tests/Db/Expression/Function/MultiOperandFunctionTest.php
@@ -0,0 +1,93 @@
+select('column')->from('table')->where(['id' => 1]);
+
+ return [
+ ArrayMerge::class => [ArrayMerge::class, [
+ [[1, 2, 3]],
+ [new ArrayExpression([1, 2, 3])],
+ [$query],
+ [[1, 2, 3], '[1,2,3]', new ArrayExpression([1, 2, 3]), $query],
+ ]],
+ Greatest::class => [Greatest::class, [
+ [1],
+ [1.5],
+ ['1 + 2'],
+ [$query],
+ [1, 1.5, '1 + 2', $query],
+ ]],
+ Least::class => [Least::class, [
+ [1],
+ [1.5],
+ ['1 + 2'],
+ [$query],
+ [1, 1.5, '1 + 2', $query],
+ ]],
+ Longest::class => [Longest::class, [
+ ['expression'],
+ [$stringValue],
+ [$query],
+ ['expression', $stringValue, $query],
+ ]],
+ Shortest::class => [Shortest::class, [
+ ['expression'],
+ [$stringValue],
+ [$query],
+ ['expression', $stringValue, $query],
+ ]],
+ ];
+ }
+
+ #[DataProvider('dataOperands')]
+ public function testConstruct(string $class, array $operandLists): void
+ {
+ $expression = new $class();
+ $this->assertInstanceOf(MultiOperandFunction::class, $expression);
+ $this->assertSame([], $expression->getOperands());
+
+ foreach ($operandLists as $operands) {
+ $expression = new $class(...$operands);
+
+ $this->assertSame($operands, $expression->getOperands());
+ }
+ }
+
+ #[DataProvider('dataOperands')]
+ public function testOperands(string $class, array $operandLists): void
+ {
+ foreach ($operandLists as $operands) {
+ $expression = new $class();
+ $this->assertSame([], $expression->getOperands());
+
+ foreach ($operands as $operand) {
+ $expression->add($operand);
+ }
+
+ $this->assertSame($operands, $expression->getOperands());
+ }
+ }
+}
diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php
index 3ef3c8396..ed5bdf65c 100644
--- a/tests/Provider/QueryBuilderProvider.php
+++ b/tests/Provider/QueryBuilderProvider.php
@@ -15,6 +15,10 @@
use Yiisoft\Db\Expression\CaseExpression;
use Yiisoft\Db\Expression\ColumnName;
use Yiisoft\Db\Expression\Expression;
+use Yiisoft\Db\Expression\Function\Greatest;
+use Yiisoft\Db\Expression\Function\Least;
+use Yiisoft\Db\Expression\Function\Longest;
+use Yiisoft\Db\Expression\Function\Shortest;
use Yiisoft\Db\Expression\JsonExpression;
use Yiisoft\Db\Expression\Value;
use Yiisoft\Db\Query\Query;
@@ -1924,4 +1928,139 @@ public static function caseExpressionBuilder(): array
],
];
}
+
+ public static function lengthBuilder(): array
+ {
+ return [
+ 'string' => [
+ "'string'",
+ "LENGTH('string')",
+ 6,
+ ],
+ 'param' => [
+ $param = new Param('string', DataType::STRING),
+ 'LENGTH(:pv0)',
+ 6,
+ [':pv0' => $param],
+ ],
+ 'query' => [
+ static::getDb()->select(new Expression("'four'")),
+ static::replaceQuotes("LENGTH((SELECT 'four'))"),
+ 4,
+ ],
+ ];
+ }
+
+ public static function multiOperandFunctionClasses(): array
+ {
+ return [
+ Greatest::class => [Greatest::class],
+ Least::class => [Least::class],
+ Longest::class => [Longest::class],
+ Shortest::class => [Shortest::class],
+ ];
+ }
+
+ public static function multiOperandFunctionBuilder(): array
+ {
+ $stringQuery = static::getDb()->select(new Expression("'longest'"));
+ $stringQuerySql = "(SELECT 'longest')";
+ $intQuery = static::getDb()->select(10);
+ $intQuerySql = '(SELECT 10)';
+ $stringParam = new Param('string', DataType::STRING);
+
+ return [
+ 'Greatest with 1 operand' => [
+ Greatest::class,
+ ['1 + 2'],
+ '(1 + 2)',
+ 3,
+ ],
+ 'Greatest with 2 operands' => [
+ Greatest::class,
+ [1, '1 + 2'],
+ 'GREATEST(1, 1 + 2)',
+ 3,
+ ],
+ 'Greatest with 4 operands' => [
+ Greatest::class,
+ [1, 1.5, '1 + 2', $intQuery],
+ "GREATEST(1, 1.5, 1 + 2, $intQuerySql)",
+ 10,
+ ],
+
+ 'Least with 1 operand' => [
+ Least::class,
+ ['1 + 2'],
+ '(1 + 2)',
+ 3,
+ ],
+ 'Least with 2 operands' => [
+ Least::class,
+ [1, '1 + 2'],
+ 'LEAST(1, 1 + 2)',
+ 1,
+ ],
+ 'Least with 4 operands' => [
+ Least::class,
+ [1, 1.5, '1 + 2', $intQuery],
+ "LEAST(1, 1.5, 1 + 2, $intQuerySql)",
+ 1,
+ ],
+
+ 'Longest with 1 operand' => [
+ Longest::class,
+ ["'string'"],
+ "('string')",
+ 'string',
+ ],
+ 'Longest with 2 operands' => [
+ Longest::class,
+ ["'short'", $stringParam],
+ static::replaceQuotes(
+ "(SELECT value FROM (SELECT 'short' AS value UNION SELECT :qp0 AS value) AS t ORDER BY LENGTH(value) DESC LIMIT 1)",
+ ),
+ 'string',
+ [':qp0' => $stringParam],
+ ],
+ 'Longest with 3 operands' => [
+ Longest::class,
+ ["'short'", $stringQuery, $stringParam],
+ static::replaceQuotes(
+ "(SELECT value FROM (SELECT 'short' AS value UNION SELECT $stringQuerySql AS value UNION SELECT :qp0 AS value) AS t ORDER BY LENGTH(value) DESC LIMIT 1)",
+ ),
+ 'longest',
+ [
+ ':qp0' => $stringParam,
+ ],
+ ],
+
+ 'Shortest with 1 operand' => [
+ Shortest::class,
+ ["'short'"],
+ "('short')",
+ 'short',
+ ],
+ 'Shortest with 2 operands' => [
+ Shortest::class,
+ ["'short'", $stringParam],
+ static::replaceQuotes(
+ "(SELECT value FROM (SELECT 'short' AS value UNION SELECT :qp0 AS value) AS t ORDER BY LENGTH(value) ASC LIMIT 1)",
+ ),
+ 'short',
+ [':qp0' => $stringParam],
+ ],
+ 'Shortest with 3 operands' => [
+ Shortest::class,
+ ["'short'", $stringQuery, $stringParam],
+ static::replaceQuotes(
+ "(SELECT value FROM (SELECT 'short' AS value UNION SELECT $stringQuerySql AS value UNION SELECT :qp0 AS value) AS t ORDER BY LENGTH(value) ASC LIMIT 1)",
+ ),
+ 'short',
+ [
+ ':qp0' => $stringParam,
+ ],
+ ],
+ ];
+ }
}