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: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, #1053: Add `CaseExpression` and `CaseExpressionBuilder` to build `CASE-WHEN-THEN-ELSE` SQL expressions (@Tigrov)
- New #988, #1053, #1067: Add `CaseX` and `CaseXBuilder` 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)
Expand Down
37 changes: 28 additions & 9 deletions docs/guide/en/expressions/statements.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
# 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:

Expand All @@ -15,11 +10,35 @@ The following expression classes are available:

The [CaseX](../../../../src/Expression/Statement/CaseX.php) expression allows you to create SQL `CASE` statements.

The `CaseX` class accepts the following arguments:

- `value` comparison condition in the `CASE` expression:
- `string` is treated as a table column name which will be quoted before usage in the SQL statement;
- `array` is treated as a condition to check, see `QueryInterface::where()`;
- other values will be converted to their string representation using `QueryBuilderInterface::buildValue()`.
If not provided, the `CASE` expression will be a WHEN-THEN structure without a specific case value.
- `valueType` optional data type of the `CASE` expression which can be used in some DBMS to specify the expected type;
- `...args` List of `WHEN-THEN` conditions and their corresponding results represented
as [WhenThen](https://github.com/yiisoft/db/blob/master/src/Expression/Statement/WhenThen.php) instances
or `ELSE` value in the `CASE` expression. String `ELSE` value will be quoted before usage in the SQL statement.

For example:

```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'),
'status',
when1: new WnenThen('active', 'Active User'),
when2: new WnenThen('inactive', 'Inactive User'),
else: 'Unknown Status',
);
```

This will generate the following SQL:

```sql
CASE "status"
WHEN 'active' THEN 'Active User'
WHEN 'inactive' THEN 'Inactive User'
ELSE 'Unknown Status'
END
```
36 changes: 18 additions & 18 deletions src/Expression/Statement/Builder/CaseXBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;

use function gettype;
use function is_string;
use function is_array;

/**
* Builds expressions for {@see CaseX}.
Expand All @@ -37,50 +37,50 @@ public function build(ExpressionInterface $expression, array &$params = []): str
$sql = 'CASE';

if ($expression->value !== null) {
$sql .= ' ' . $this->buildCondition($expression->value, $params);
$sql .= ' ' . $this->buildCaseValue($expression->value, $params);
}

foreach ($expression->when as $when) {
$sql .= ' WHEN ' . $this->buildCondition($when->condition, $params);
$sql .= ' THEN ' . $this->buildResult($when->result, $params);
foreach ($expression->whenThen as $whenThen) {
$sql .= ' WHEN ' . $this->buildCondition($whenThen->when, $params);
$sql .= ' THEN ' . $this->queryBuilder->buildValue($whenThen->then, $params);
}

if ($expression->hasElse()) {
$sql .= ' ELSE ' . $this->buildResult($expression->else, $params);
$sql .= ' ELSE ' . $this->queryBuilder->buildValue($expression->else, $params);
}

return $sql . ' END';
}

/**
* Builds the condition part of the CASE expression based on their type.
* Builds the case value part of the CASE expression based on their type.
*
* @return string The SQL condition string.
*/
protected function buildCondition(mixed $condition, array &$params): string
protected function buildCaseValue(mixed $value, array &$params): string
{
/**
* @var string
* @psalm-suppress MixedArgument
*/
return match (gettype($condition)) {
GettypeResult::ARRAY => $this->queryBuilder->buildCondition($condition, $params),
GettypeResult::STRING => $condition,
default => $this->queryBuilder->buildValue($condition, $params),
return match (gettype($value)) {
GettypeResult::ARRAY => $this->queryBuilder->buildCondition($value, $params),
GettypeResult::STRING => $this->queryBuilder->getQuoter()->quoteColumnName($value),
default => $this->queryBuilder->buildValue($value, $params),
};
}

/**
* Builds the result part of the `CASE` expression based on its type.
* Builds the condition part of the CASE expression based on their type.
*
* @return string The SQL result string.
* @return string The SQL condition string.
*/
protected function buildResult(mixed $result, array &$params): string
protected function buildCondition(mixed $condition, array &$params): string
{
if (is_string($result)) {
return $result;
if (is_array($condition)) {
return $this->queryBuilder->buildCondition($condition, $params);
}

return $this->queryBuilder->buildValue($result, $params);
return $this->queryBuilder->buildValue($condition, $params);
}
}
57 changes: 29 additions & 28 deletions src/Expression/Statement/CaseX.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,49 +22,49 @@
*
* ```php
* $case = new CaseX(
* when1: new When('condition1', 'result1'),
* when2: new When('condition2', 'result2'),
* else: 'defaultResult',
* when1: new WhenThen(true, 'result1'),
* when2: new WhenThen(false, 'result2'),
* else: 'default result',
* );
* ```
*
* This will be generated into a SQL `CASE` expression like:
*
* ```sql
* CASE
* WHEN condition1 THEN result1
* WHEN condition2 THEN result2
* ELSE defaultResult
* WHEN TRUE THEN 'result1'
* WHEN FALSE THEN 'result2'
* ELSE 'default result'
* END
* ```
*
* Example with a specific case value:
*
* ```php
* $case = new CaseX(
* 'expression',
* when1: new When(1, 'result1'),
* when2: new When(2, 'result2'),
* else: 'defaultResult',
* 'column_name',
* when1: new WhenThen('one', 'result1'),
* when2: new WhenThen('two', 'result2'),
* else: 'default result',
* );
* ```
*
* This will be generated into a SQL `CASE` expression like:
*
* ```sql
* CASE expression
* WHEN 1 THEN result1
* WHEN 2 THEN result2
* ELSE defaultResult
* CASE "column_name"
* WHEN 'one' THEN 'result1'
* WHEN 'two' THEN 'result2'
* ELSE 'default result'
* END
* ```
*/
final class CaseX implements ExpressionInterface
{
/**
* @var When[] List of `WHEN` conditions and their corresponding results in the `CASE` expression.
* @var WhenThen[] List of `WHEN-THEN` conditions and their corresponding results in the `CASE` expression.
*/
public readonly array $when;
public readonly array $whenThen;
/**
* @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.
Expand All @@ -75,37 +75,38 @@ final class CaseX implements ExpressionInterface

/**
* @param mixed $value Comparison condition in the `CASE` expression:
* - `string` is treated as a SQL expression;
* - `string` is treated as a table column name which will be quoted before usage in the SQL statement;
* - `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 $valueType Optional data type of the CASE expression which can be used in some DBMS
* to specify the expected type (for example in PostgreSQL).
* @param mixed|When ...$args List of `WHEN` conditions and their corresponding results represented
* as {@see When} instances or `ELSE` value in the `CASE` expression.
* If not provided, the `CASE` expression will be a `WHEN-THEN` structure without a specific case value.
* @param ColumnInterface|string $valueType Optional data type of the `CASE` expression which can be used
* in some DBMS to specify the expected type (for example, in PostgreSQL).
* @param mixed|WhenThen ...$args List of `WHEN-THEN` conditions and their corresponding results represented
* as {@see WhenThen} instances or `ELSE` value in the `CASE` expression. String `ELSE` value will be quoted
* before usage in the SQL statement.
*/
public function __construct(
public readonly mixed $value = null,
public readonly string|ColumnInterface $valueType = '',
mixed ...$args,
) {
$when = [];
$whenThen = [];

foreach ($args as $arg) {
if ($arg instanceof When) {
$when[] = $arg;
if ($arg instanceof WhenThen) {
$whenThen[] = $arg;
} elseif ($this->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.');
if (empty($whenThen)) {
throw new InvalidArgumentException('`CASE` expression must have at least one `WHEN-THEN` clause.');
}

$this->when = $when;
$this->whenThen = $whenThen;
}

/**
Expand Down
31 changes: 0 additions & 31 deletions src/Expression/Statement/When.php

This file was deleted.

29 changes: 29 additions & 0 deletions src/Expression/Statement/WhenThen.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Expression\Statement;

use Yiisoft\Db\Query\QueryInterface;
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;

/**
* Represents the condition and the result of a `WHEN-THEN` clause in a SQL `CASE` statement.
*
* @see CaseX
*/
final class WhenThen
{
/**
* @param mixed $when The value or condition for the `WHEN-THEN` clause:
* - `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`. The value will be converted to its string
* representation using {@see QueryBuilderInterface::buildValue()}.
*/
public function __construct(
public readonly mixed $when,
public readonly mixed $then,
) {
}
}
Loading