diff --git a/CHANGELOG.md b/CHANGELOG.md index 99582792f..66ec80bad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,7 +103,7 @@ - New #984: Add `createQuery()` and `select()` methods to `ConnectionInterface` (@Tigrov) - Chg #985: Rename `insertWithReturningPks()` to `insertReturningPks()` in `CommandInterface` and `DMLQueryBuilderInterface` (@Tigrov) - Enh #992: Add optional type casting to `DataReaderInterface` using columns (@Tigrov) -- New #988: Add `CaseExpression` and `CaseExpressionBuilder` to build `CASE-WHEN-THEN-ELSE` SQL expressions (@Tigrov) +- New #988, #1053: Add `CaseExpression` and `CaseExpressionBuilder` to build `CASE-WHEN-THEN-ELSE` SQL expressions (@Tigrov) - Enh #991: Improve types in `ConnectionInterface::transaction()` (@kikara) - Chg #998: Add `yiisoft/db-implementation` virtual package as dependency (@vjik) - Chg #999: Remove `requireTransaction()` method and `$isolationLevel` property from `AbstractCommand` (@vjik) diff --git a/docs/guide/en/expressions/expressions.md b/docs/guide/en/expressions/expressions.md index cccb9ee20..48906e3e6 100644 --- a/docs/guide/en/expressions/expressions.md +++ b/docs/guide/en/expressions/expressions.md @@ -10,3 +10,4 @@ of the `Yiisoft\Db\Query\QueryInterface` and the `insert()`, `update()`, `upsert ## Available expressions - [Expression](expression.md) that does not require escaping or quoting of its content; - [Function expressions](functions.md) `ArrayMerge`, `Greatest`, `Least`, `Length`, `Longest`, `Shortest`; +- [Statement expressions](statements.md) `CaseX`; diff --git a/docs/guide/en/expressions/functions.md b/docs/guide/en/expressions/functions.md index 350f49850..063db190b 100644 --- a/docs/guide/en/expressions/functions.md +++ b/docs/guide/en/expressions/functions.md @@ -1,10 +1,10 @@ -# Function expressions +# Function Expressions The library provides several classes to represent SQL functions as expressions. > [!WARNING] > The functions do not quote string values or column names, use [Value](../../../../src/Expression/Value/Value.php) -> expression for string values and [ColumnName](../../../../src/Expression/Value/ColumnName.php) expression for column names +> object for string values and [ColumnName](../../../../src/Expression/Value/ColumnName.php) object for column names > or quote the values directly. > > For example, `new Longest(new Value('short'), new ColumnName('column'), "'longest'")` diff --git a/docs/guide/en/expressions/statements.md b/docs/guide/en/expressions/statements.md new file mode 100644 index 000000000..d55f5924f --- /dev/null +++ b/docs/guide/en/expressions/statements.md @@ -0,0 +1,25 @@ +# Statement Expressions + +The library provides classes to represent SQL statements as expressions. + +> [!WARNING] +> The statements do not quote string values or column names, use [Value](../../../../src/Expression/Value/Value.php) +> object for string values and [ColumnName](../../../../src/Expression/Value/ColumnName.php) object for column names +> or quote the values directly. + +The following expression classes are available: + +- [CaseX](#casex) + +## CaseX + +The [CaseX](../../../../src/Expression/Statement/CaseX.php) expression allows you to create SQL `CASE` statements. + +```php +$case = new CaseX( + new Column('status'), + when1: new Wnen(new Value('active'), new Value('Active User')), + when2: new Wnen("'inactive'", "'Inactive User'"), + else: new Value('Unknown Status'), +); +``` diff --git a/docs/guide/en/query/where.md b/docs/guide/en/query/where.md index 418f49170..4556ff215 100644 --- a/docs/guide/en/query/where.md +++ b/docs/guide/en/query/where.md @@ -139,11 +139,11 @@ Operand 1 should be the column name, and operand 2 and 3 should be the starting For example, `['between', 'id', 1,10]` will generate `id BETWEEN 1 AND 10`. -You can also use `Yiisoft\Db\Expression\ColumnName` and `Yiisoft\Db\Expression\Value` expressions: +You can also use `Yiisoft\Db\Expression\Value\ColumnName` and `Yiisoft\Db\Expression\Value\Value` expressions: ```php -use Yiisoft\Db\Expression\ColumnName; -use Yiisoft\Db\Expression\Value; +use Yiisoft\Db\Expression\Value\ColumnName; +use Yiisoft\Db\Expression\Value\Value; ['between', new Value('2025-08-11'), new ColumnName('start_date'), new ColumnName('end_date')] ``` diff --git a/src/Expression/Statement/Builder/CaseExpressionBuilder.php b/src/Expression/Statement/Builder/CaseXBuilder.php similarity index 64% rename from src/Expression/Statement/Builder/CaseExpressionBuilder.php rename to src/Expression/Statement/Builder/CaseXBuilder.php index 4c564817c..191735f9e 100644 --- a/src/Expression/Statement/Builder/CaseExpressionBuilder.php +++ b/src/Expression/Statement/Builder/CaseXBuilder.php @@ -4,10 +4,9 @@ namespace Yiisoft\Db\Expression\Statement\Builder; -use InvalidArgumentException; use Yiisoft\Db\Constant\GettypeResult; use Yiisoft\Db\Expression\ExpressionBuilderInterface; -use Yiisoft\Db\Expression\Statement\CaseExpression; +use Yiisoft\Db\Expression\Statement\CaseX; use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; @@ -15,47 +14,39 @@ use function is_string; /** - * Builds expressions for {@see CaseExpression}. + * Builds expressions for {@see CaseX}. * - * @implements ExpressionBuilderInterface + * @implements ExpressionBuilderInterface */ -class CaseExpressionBuilder implements ExpressionBuilderInterface +class CaseXBuilder implements ExpressionBuilderInterface { public function __construct(protected readonly QueryBuilderInterface $queryBuilder) { } /** - * Builds an SQL CASE expression from the given {@see CaseExpression} object. + * Builds an SQL `CASE` expression from the given {@see CaseX} object. * - * @param CaseExpression $expression The CASE expression to build. + * @param CaseX $expression The `CASE` expression to build. * @param array $params The parameters to be bound to the query. * - * @return string SQL CASE expression. + * @return string SQL `CASE` expression. */ public function build(ExpressionInterface $expression, array &$params = []): string { - $whenClauses = $expression->getWhen(); - - if (empty($whenClauses)) { - throw new InvalidArgumentException('The CASE expression must have at least one WHEN clause.'); - } - $sql = 'CASE'; - $case = $expression->getCase(); - - if ($case !== null) { - $sql .= ' ' . $this->buildCondition($case, $params); + if ($expression->value !== null) { + $sql .= ' ' . $this->buildCondition($expression->value, $params); } - foreach ($whenClauses as $when) { + foreach ($expression->when as $when) { $sql .= ' WHEN ' . $this->buildCondition($when->condition, $params); $sql .= ' THEN ' . $this->buildResult($when->result, $params); } if ($expression->hasElse()) { - $sql .= ' ELSE ' . $this->buildResult($expression->getElse(), $params); + $sql .= ' ELSE ' . $this->buildResult($expression->else, $params); } return $sql . ' END'; @@ -80,7 +71,7 @@ protected function buildCondition(mixed $condition, array &$params): string } /** - * Builds the result part of the CASE expression based on its type. + * Builds the result part of the `CASE` expression based on its type. * * @return string The SQL result string. */ diff --git a/src/Expression/Statement/CaseExpression.php b/src/Expression/Statement/CaseExpression.php deleted file mode 100644 index 342204355..000000000 --- a/src/Expression/Statement/CaseExpression.php +++ /dev/null @@ -1,208 +0,0 @@ -addWhen('condition1', 'result1') - * ->addWhen('condition2', 'result2') - * ->else('defaultResult'); - * ``` - * - * This will be generated into a SQL CASE expression like: - * - * ```sql - * CASE - * WHEN condition1 THEN result1 - * WHEN condition2 THEN result2 - * ELSE defaultResult - * END - * ``` - * - * Example with a specific case value: - * - * ```php - * $case = (new CaseExpression('expression')) - * ->addWhen(1, 'result1') - * ->addWhen(2, 'result2') - * ->else('defaultResult'); - * ``` - * - * This will be generated into a SQL CASE expression like: - * - * ```sql - * CASE expression - * WHEN 1 THEN result1 - * WHEN 2 THEN result2 - * ELSE defaultResult - * END - * ``` - */ -final class CaseExpression implements ExpressionInterface -{ - /** - * @var WhenClause[] List of WHEN conditions and their corresponding results in the CASE expression. - */ - private array $whenClauses; - /** - * @var mixed The result to return if no conditions match in the CASE expression. - * If not set, the CASE expression will not have an ELSE clause. - * - * @psalm-suppress PropertyNotSetInConstructor - */ - private mixed $else; - - /** - * @param mixed $case Comparison condition in the CASE expression: - * - `string` is treated as a SQL expression; - * - `array` is treated as a condition to check, see {@see QueryInterface::where()}; - * - other values will be converted to their string representation using {@see QueryBuilderInterface::buildValue()}. - * If not provided, the CASE expression will be a WHEN-THEN structure without a specific case value. - * @param ColumnInterface|string $caseType Optional data type of the CASE expression which can be used in some DBMS - * to specify the expected type (for example in PostgreSQL). - * @param WhenClause ...$when List of WHEN conditions and their corresponding results in the CASE expression. - */ - public function __construct( - private mixed $case = null, - private string|ColumnInterface $caseType = '', - WhenClause ...$when, - ) { - $this->whenClauses = $when; - } - - /** - * Adds a condition and its corresponding result to the CASE expression. - * - * @param mixed $when The condition to check (WHEN): - * - `string` is treated as a SQL expression; - * - `array` is treated as a condition to check, see {@see QueryInterface::where()}; - * - other values will be converted to their string representation using {@see QueryBuilderInterface::buildValue()}. - * @param mixed $then The result to return if the condition is `true` (THEN): - * - `string` is treated as a SQL expression; - * - other values will be converted to their string representation using {@see QueryBuilderInterface::buildValue()}. - */ - public function addWhen(mixed $when, mixed $then): self - { - $this->whenClauses[] = new WhenClause($when, $then); - return $this; - } - - /** - * Sets the value to compare against in the CASE expression. - * - * @param mixed $case Comparison condition in the CASE expression: - * - `string` is treated as a SQL expression; - * - `array` is treated as a condition to check, see {@see QueryInterface::where()}; - * - other values will be converted to their string representation using {@see QueryBuilderInterface::buildValue()}. - * If not provided, the CASE expression will be a WHEN-THEN structure without a specific case value. - */ - public function case(mixed $case): self - { - $this->case = $case; - return $this; - } - - /** - * Sets the optional data type of the CASE expression which can be used in some DBMS to specify the expected type - * (for example in PostgreSQL). - */ - public function caseType(string|ColumnInterface $caseType): self - { - $this->caseType = $caseType; - return $this; - } - - /** - * Sets the result to return if no conditions match in the CASE expression. - * - * @param mixed $else The result to return if no conditions match (ELSE). - * - `string` is treated as a SQL expression; - * - other values will be converted to their string representation using {@see QueryBuilderInterface::buildValue()}. - * If not set, the CASE expression will not have an ELSE clause. - */ - public function else(mixed $else): self - { - $this->else = $else; - return $this; - } - - /** - * Returns the comparison condition in the CASE expression. - * - * @psalm-mutation-free - */ - public function getCase(): mixed - { - return $this->case; - } - - /** - * Returns the data type of the CASE expression. - * - * @psalm-mutation-free - */ - public function getCaseType(): string|ColumnInterface - { - return $this->caseType; - } - - /** - * Returns the result to return if no conditions match in the CASE expression. - * - * @psalm-mutation-free - */ - public function getElse(): mixed - { - return $this->else ?? null; - } - - /** - * Returns WHEN conditions and their corresponding results in the CASE expression. - * - * @return WhenClause[] List of WHEN conditions and their corresponding results in the CASE expression. - * - * @psalm-mutation-free - */ - public function getWhen(): array - { - return $this->whenClauses; - } - - /** - * Returns `true` if the CASE expression has an ELSE clause, `false` otherwise. - * - * @psalm-mutation-free - */ - public function hasElse(): bool - { - return array_key_exists('else', get_object_vars($this)); - } - - /** - * Sets WHEN conditions and their corresponding results in the CASE expression. - * - * @param WhenClause ...$whenClauses List of WHEN conditions and their corresponding results in the CASE expression. - */ - public function setWhen(WhenClause ...$whenClauses): self - { - $this->whenClauses = $whenClauses; - return $this; - } -} diff --git a/src/Expression/Statement/CaseX.php b/src/Expression/Statement/CaseX.php new file mode 100644 index 000000000..55cf4ddfa --- /dev/null +++ b/src/Expression/Statement/CaseX.php @@ -0,0 +1,120 @@ +hasElse()) { + throw new InvalidArgumentException('`CASE` expression can have only one `ELSE` value.'); + } else { + $this->else = $arg; + } + } + + if (empty($when)) { + throw new InvalidArgumentException('`CASE` expression must have at least one `WHEN` clause.'); + } + + $this->when = $when; + } + + /** + * Returns `true` if the `CASE` expression has an `ELSE` clause, `false` otherwise. + * + * @psalm-mutation-free + */ + public function hasElse(): bool + { + return array_key_exists('else', get_object_vars($this)); + } +} diff --git a/src/Expression/Statement/WhenClause.php b/src/Expression/Statement/When.php similarity index 80% rename from src/Expression/Statement/WhenClause.php rename to src/Expression/Statement/When.php index c8455d46a..314319fb1 100644 --- a/src/Expression/Statement/WhenClause.php +++ b/src/Expression/Statement/When.php @@ -8,14 +8,14 @@ use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; /** - * Represents the condition and the result of a WHEN clause in a SQL CASE statement. + * Represents the condition and the result of a `WHEN` clause in a SQL `CASE` statement. * - * @see CaseExpression + * @see CaseX */ -final class WhenClause +final class When { /** - * @param mixed $condition The condition for the WHEN clause: + * @param mixed $condition The condition for the `WHEN` clause: * - `string` is treated as a SQL expression; * - `array` is treated as a condition to check, see {@see QueryInterface::where()}; * - other values will be converted to their string representation using {@see QueryBuilderInterface::buildValue()}. diff --git a/src/QueryBuilder/AbstractDQLQueryBuilder.php b/src/QueryBuilder/AbstractDQLQueryBuilder.php index 7b3d4aa02..253964d43 100644 --- a/src/QueryBuilder/AbstractDQLQueryBuilder.php +++ b/src/QueryBuilder/AbstractDQLQueryBuilder.php @@ -30,8 +30,8 @@ use Yiisoft\Db\Expression\Function\Shortest; use Yiisoft\Db\Expression\Value\JsonExpression; use Yiisoft\Db\Expression\Value\Builder\JsonExpressionBuilder; -use Yiisoft\Db\Expression\Statement\CaseExpression; -use Yiisoft\Db\Expression\Statement\Builder\CaseExpressionBuilder; +use Yiisoft\Db\Expression\Statement\CaseX; +use Yiisoft\Db\Expression\Statement\Builder\CaseXBuilder; use Yiisoft\Db\Expression\Value\StructuredExpression; use Yiisoft\Db\Expression\Value\Builder\StructuredExpressionBuilder; use Yiisoft\Db\Expression\Value\Value; @@ -583,7 +583,7 @@ protected function defaultExpressionBuilders(): array JsonExpression::class => JsonExpressionBuilder::class, ArrayExpression::class => ArrayExpressionBuilder::class, StructuredExpression::class => StructuredExpressionBuilder::class, - CaseExpression::class => CaseExpressionBuilder::class, + CaseX::class => CaseXBuilder::class, ColumnName::class => ColumnNameBuilder::class, Value::class => ValueBuilder::class, DateTimeValue::class => DateTimeValueBuilder::class, diff --git a/tests/AbstractQueryBuilderTest.php b/tests/AbstractQueryBuilderTest.php index 32a945d7c..2541e078a 100644 --- a/tests/AbstractQueryBuilderTest.php +++ b/tests/AbstractQueryBuilderTest.php @@ -16,7 +16,7 @@ use InvalidArgumentException; use Yiisoft\Db\Exception\InvalidConfigException; use Yiisoft\Db\Exception\NotSupportedException; -use Yiisoft\Db\Expression\Statement\CaseExpression; +use Yiisoft\Db\Expression\Statement\CaseX; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Expression\ExpressionBuilderInterface; use Yiisoft\Db\Expression\ExpressionInterface; @@ -2418,9 +2418,9 @@ public function testBuildValue(mixed $value, string $expected, array $expectedPa Assert::arraysEquals($expectedParams, $params); } - #[DataProviderExternal(QueryBuilderProvider::class, 'caseExpressionBuilder')] - public function testCaseExpressionBuilder( - CaseExpression $case, + #[DataProviderExternal(QueryBuilderProvider::class, 'caseXBuilder')] + public function testCaseXBuilder( + CaseX $case, string $expectedSql, array $expectedParams, string|int $expectedResult, @@ -2434,20 +2434,6 @@ public function testCaseExpressionBuilder( $this->assertEquals($expectedParams, $params); } - public function testCaseExpressionBuilderEmpty(): void - { - $db = $this->getConnection(); - $qb = $db->getQueryBuilder(); - - $params = []; - $case = new CaseExpression(); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The CASE expression must have at least one WHEN clause.'); - - $qb->buildExpression($case, $params); - } - #[DataProviderExternal(QueryBuilderProvider::class, 'lengthBuilder')] public function testLengthBuilder( string|ExpressionInterface $operand, diff --git a/tests/Common/CommonQueryBuilderTest.php b/tests/Common/CommonQueryBuilderTest.php index 1a2c2e1b0..1e092ea59 100644 --- a/tests/Common/CommonQueryBuilderTest.php +++ b/tests/Common/CommonQueryBuilderTest.php @@ -12,7 +12,7 @@ use Yiisoft\Db\Expression\Value\Param; use Yiisoft\Db\Constant\DataType; use Yiisoft\Db\Exception\Exception; -use Yiisoft\Db\Expression\Statement\CaseExpression; +use Yiisoft\Db\Expression\Statement\CaseX; use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Expression\Function\Length; use Yiisoft\Db\Expression\Value\DateTimeValue; @@ -195,14 +195,14 @@ public function testUpdateWithoutTypecasting(): void ], $params); } - #[DataProviderExternal(QueryBuilderProvider::class, 'caseExpressionBuilder')] - public function testCaseExpressionBuilder( - CaseExpression $case, + #[DataProviderExternal(QueryBuilderProvider::class, 'caseXBuilder')] + public function testCaseXBuilder( + CaseX $case, string $expectedSql, array $expectedParams, string|int $expectedResult, ): void { - parent::testCaseExpressionBuilder($case, $expectedSql, $expectedParams, $expectedResult); + parent::testCaseXBuilder($case, $expectedSql, $expectedParams, $expectedResult); $db = $this->getConnection(); diff --git a/tests/Db/Expression/Value/CaseExpressionTest.php b/tests/Db/Expression/Value/CaseExpressionTest.php deleted file mode 100644 index 707e2975f..000000000 --- a/tests/Db/Expression/Value/CaseExpressionTest.php +++ /dev/null @@ -1,170 +0,0 @@ - [null], - 'string' => ['field = 1'], - 'expression' => [new Expression('field = 1')], - 'boolean' => [true], - 'float' => [2.3], - 'int' => [1], - 'array' => [['=', 'field', 1]], - ]; - } - - #[DataProvider('dataCase')] - public function testConstruct(mixed $case): void - { - $expression = new CaseExpression($case); - - $this->assertSame($case, $expression->getCase()); - $this->assertSame('', $expression->getCaseType()); - $this->assertSame([], $expression->getWhen()); - } - - public function testConstructType(): void - { - $expression = new CaseExpression(caseType: 'int'); - - $this->assertNull($expression->getCase()); - $this->assertSame('int', $expression->getCaseType()); - - $intCol = new IntegerColumn(); - $expression = new CaseExpression(caseType: $intCol); - $this->assertNull($expression->getCase()); - $this->assertSame($intCol, $expression->getCaseType()); - $this->assertSame([], $expression->getWhen()); - } - - public function testConstructorWhenClauses() - { - // Test with one when clauses - $whenClause = new WhenClause('field = 1', 'result1'); - $expression = new CaseExpression(when: $whenClause); - - $this->assertNull($expression->getCase()); - $this->assertSame('', $expression->getCaseType()); - $this->assertSame(['when' => $whenClause], $expression->getWhen()); - - // Test with multiple when clauses - $whenClauses = [ - 'when0' => new WhenClause('field = 1', 'result1'), - 'when1' => new WhenClause('field = 2', 'result2'), - ]; - $expression = new CaseExpression(...$whenClauses); - - $this->assertNull($expression->getCase()); - $this->assertSame('', $expression->getCaseType()); - $this->assertSame($whenClauses, $expression->getWhen()); - } - - #[DataProvider('dataCase')] - public function testCase(mixed $case): void - { - $expression = new CaseExpression(); - - $this->assertNull($expression->getCase()); - - $expression->case($case); - - $this->assertSame($case, $expression->getCase()); - } - - public function testCaseType(): void - { - $expression = new CaseExpression(); - - $this->assertSame('', $expression->getCaseType()); - - $expression->caseType('int'); - - $this->assertSame('int', $expression->getCaseType()); - - $intCol = new IntegerColumn(); - $expression->caseType($intCol); - - $this->assertSame($intCol, $expression->getCaseType()); - } - - public function testWhen(): void - { - $expression = new CaseExpression(); - - $this->assertSame([], $expression->getWhen()); - - $expression->addWhen('field = 1', 'result1'); - $expression->addWhen('field = 2', 'result2'); - - Assert::arraysEquals( - [ - new WhenClause('field = 1', 'result1'), - new WhenClause('field = 2', 'result2'), - ], - $expression->getWhen(), - ); - - $expression->setWhen(); - - $this->assertSame([], $expression->getWhen()); - - $whenClauses = [new WhenClause('field = 3', 'result3')]; - $expression->setWhen(...$whenClauses); - - $this->assertSame($whenClauses, $expression->getWhen()); - - $whenClauses = [ - new WhenClause('field = 3', 'result3'), - new WhenClause('field = 4', 'result4'), - ]; - $expression->setWhen(...$whenClauses); - - $this->assertSame($whenClauses, $expression->getWhen()); - } - - public function testElse(): void - { - $expression = new CaseExpression(); - - $this->assertFalse($expression->hasElse()); - $this->assertNull($expression->getElse()); - - $expression->else(null); - - $this->assertTrue($expression->hasElse()); - $this->assertNull($expression->getElse()); - - $expression->else('result'); - - $this->assertTrue($expression->hasElse()); - $this->assertSame('result', $expression->getElse()); - } - - public function testWhenClause(): void - { - $when = new WhenClause('field = 1', 'result1'); - - $this->assertSame('field = 1', $when->condition); - $this->assertSame('result1', $when->result); - } -} diff --git a/tests/Db/Expression/Value/CaseXTest.php b/tests/Db/Expression/Value/CaseXTest.php new file mode 100644 index 000000000..16622cf61 --- /dev/null +++ b/tests/Db/Expression/Value/CaseXTest.php @@ -0,0 +1,116 @@ + [null], + 'string' => ['field = 1'], + 'expression' => [new Expression('field = 1')], + 'boolean' => [true], + 'float' => [2.3], + 'int' => [1], + 'array' => [['=', 'field', 1]], + ]; + } + + #[DataProvider('dataValues')] + public function testConstruct(mixed $value): void + { + $case = new CaseX($value, when: $when = new When(1, 2)); + + $this->assertSame($value, $case->value); + $this->assertSame('', $case->valueType); + $this->assertSame([$when], $case->when); + } + + public function testConstructType(): void + { + $case = new CaseX(valueType: 'int', when: new When(1, 2)); + + $this->assertNull($case->value); + $this->assertSame('int', $case->valueType); + + $intCol = new IntegerColumn(); + $case = new CaseX(valueType: $intCol, when: new When(1, 2)); + $this->assertNull($case->value); + $this->assertSame($intCol, $case->valueType); + } + + public function testConstructorWhen() + { + // Test with one when clauses + $when = new When('field = 1', 'result1'); + $case = new CaseX(when: $when); + + $this->assertNull($case->value); + $this->assertSame('', $case->valueType); + $this->assertSame([$when], $case->when); + + // Test with multiple when clauses + $when = [ + 'when0' => new When('field = 1', 'result1'), + 'when1' => new When('field = 2', 'result2'), + ]; + $case = new CaseX(...$when); + + $this->assertNull($case->value); + $this->assertSame('', $case->valueType); + $this->assertSame(array_values($when), $case->when); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('`CASE` expression must have at least one `WHEN` clause.'); + + new CaseX(); + } + + public function testElse(): void + { + $case = new CaseX(when: new When(1, 2)); + + $this->assertFalse($case->hasElse()); + $this->assertFalse(isset($case->else)); + + $case = new CaseX(when: new When(1, 2), else: null); + + $this->assertTrue($case->hasElse()); + $this->assertNull($case->else); + + $case = new CaseX(when: new When(1, 2), else: 'result'); + + $this->assertTrue($case->hasElse()); + $this->assertSame('result', $case->else); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('`CASE` expression can have only one `ELSE` value.'); + + new CaseX(when: new When(1, 2), else1: 'result1', else2: 'result2'); + } + + public function testWhen(): void + { + $when = new When('field = 1', 'result1'); + + $this->assertSame('field = 1', $when->condition); + $this->assertSame('result1', $when->result); + } +} diff --git a/tests/Db/QueryBuilder/QueryBuilderTest.php b/tests/Db/QueryBuilder/QueryBuilderTest.php index 2826f5c52..11a1b488d 100644 --- a/tests/Db/QueryBuilder/QueryBuilderTest.php +++ b/tests/Db/QueryBuilder/QueryBuilderTest.php @@ -238,7 +238,7 @@ public function testUpsert( string $table, array|QueryInterface $insertColumns, array|bool $updateColumns, - string|array $expectedSQL, + string|array $expectedSql, array $expectedParams ): void { $db = $this->getConnection(); diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index 674cd1958..4d660e77d 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -8,6 +8,7 @@ use DateTimeImmutable; use DateTimeZone; use Yiisoft\Db\Constant\DataType; +use Yiisoft\Db\Expression\Statement\When; use Yiisoft\Db\Expression\Value\ArrayExpression; use Yiisoft\Db\Expression\Function\ArrayMerge; use Yiisoft\Db\Expression\Value\Param; @@ -16,7 +17,7 @@ use Yiisoft\Db\Constant\PseudoType; use Yiisoft\Db\Constant\ReferentialAction; use Yiisoft\Db\Constraint\ForeignKey; -use Yiisoft\Db\Expression\Statement\CaseExpression; +use Yiisoft\Db\Expression\Statement\CaseX; use Yiisoft\Db\Expression\Value\ColumnName; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Expression\Function\Greatest; @@ -1904,32 +1905,38 @@ public static function buildValue(): array ]; } - public static function caseExpressionBuilder(): array + public static function caseXBuilder(): array { return [ 'with case expression' => [ - (new CaseExpression('(1 + 2)')) - ->addWhen(1, 1) - ->addWhen(2, new Expression('2')) - ->addWhen(3, '(2 + 1)') - ->else($param = new Param(4, DataType::INTEGER)), + new CaseX( + '(1 + 2)', + when1: new When(1, 1), + when2: new When(2, new Expression('2')), + when3: new When(3, '(2 + 1)'), + else: $param = new Param(4, DataType::INTEGER), + ), 'CASE (1 + 2) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN (2 + 1) ELSE :qp0 END', [':qp0' => $param], 3, ], 'without case expression' => [ - (new CaseExpression()) - ->addWhen(['=', 'column_name', 1], $paramA = new Param('a', DataType::STRING)) - ->addWhen( + new CaseX( + when1: new When(['=', 'column_name', 1], new Value('a')), + when2: new When( static::replaceQuotes('[[column_name]] = 2'), - (new Query(self::getDb()))->select($paramB = new Param('b', DataType::STRING)) + (new Query(self::getDb()))->select($param = new Param('b', DataType::STRING)) ), + ), static::replaceQuotes( << $paramA, ':qp1' => $paramB], + [ + ':qp0' => new Param('a', DataType::STRING), + ':qp1' => $param, + ], 'b', ], ];