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, + ], + ], + ]; + } }