-
-
Notifications
You must be signed in to change notification settings - Fork 45
Add CaseExpression
#988
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add CaseExpression
#988
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
90f0dbe
Add `CaseExpression`
Tigrov 230b773
Apply Rector changes (CI)
Tigrov 9274455
Fix rector
Tigrov 15eb8ac
Fix tests
Tigrov f581334
Update doc [skip ci]
Tigrov a6bb3d7
Add type of case expression
Tigrov 19111f8
Apply fixes from StyleCI
StyleCIBot e8d4916
Improve
Tigrov d6b7fad
Change result type to `mixed`
Tigrov bf9d41c
Merge branch 'refs/heads/master' into case-expression
Tigrov a8cf089
Improve tests
Tigrov 640ed20
Apply fixes from StyleCI
StyleCIBot 63f8066
Improve tests
Tigrov 05e8834
Make condition `mixed` type
Tigrov bc17bc5
Add when clauses to constructor
Tigrov 6bf5b53
Apply fixes from StyleCI
StyleCIBot 18c47f8
Improve tests
Tigrov 1bb91c5
Add line to CHANGELOG.md [skip ci]
Tigrov 2e6c917
Merge branch 'master' into case-expression
Tigrov dd121d7
Update doc [skip ci]
Tigrov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Yiisoft\Db\Expression; | ||
|
|
||
| use Yiisoft\Db\Query\QueryInterface; | ||
| use Yiisoft\Db\Schema\Column\ColumnInterface; | ||
|
|
||
| use function array_key_exists; | ||
| use function get_object_vars; | ||
|
|
||
| /** | ||
| * Represents a SQL CASE expression. | ||
| * | ||
| * A CASE expression allows conditional logic in SQL queries, returning different values based on specified conditions. | ||
| * It can be used to implement complex logic directly in SQL statements. | ||
| * | ||
| * Example usage: | ||
| * | ||
| * ```php | ||
| * $case = (new CaseExpression()) | ||
| * ->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; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Yiisoft\Db\Expression; | ||
|
|
||
| use InvalidArgumentException; | ||
| use Yiisoft\Db\Constant\GettypeResult; | ||
| use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; | ||
|
|
||
| use function gettype; | ||
| use function is_string; | ||
|
|
||
| /** | ||
| * Builds expressions for {@see CaseExpression}. | ||
| */ | ||
| class CaseExpressionBuilder implements ExpressionBuilderInterface | ||
| { | ||
| public function __construct(protected readonly QueryBuilderInterface $queryBuilder) | ||
| { | ||
| } | ||
|
|
||
| /** | ||
| * Builds an SQL CASE expression from the given {@see CaseExpression} object. | ||
| * | ||
| * @param CaseExpression $expression The CASE expression to build. | ||
| * @param array $params The parameters to be bound to the query. | ||
| * | ||
| * @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); | ||
| } | ||
|
|
||
| foreach ($whenClauses 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); | ||
| } | ||
|
|
||
| return $sql . ' END'; | ||
| } | ||
|
|
||
| /** | ||
| * Builds the condition part of the CASE expression based on their type. | ||
| * | ||
| * @return string The SQL condition string. | ||
| */ | ||
| protected function buildCondition(mixed $condition, 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), | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Builds the result part of the CASE expression based on its type. | ||
| * | ||
| * @return string The SQL result string. | ||
| */ | ||
| protected function buildResult(mixed $result, array &$params): string | ||
| { | ||
| if (is_string($result)) { | ||
| return $result; | ||
| } | ||
|
|
||
| return $this->queryBuilder->buildValue($result, $params); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Yiisoft\Db\Expression; | ||
|
|
||
| use Yiisoft\Db\Query\QueryInterface; | ||
| use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; | ||
|
|
||
| /** | ||
| * Represents the condition and the result of a WHEN clause in a SQL CASE statement. | ||
| * | ||
| * @see CaseExpression | ||
| */ | ||
| final class WhenClause | ||
vjik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| /** | ||
| * @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()}. | ||
| * @param mixed $result The result to return if the condition is `true`: | ||
| * - `string` is treated as a SQL expression; | ||
| * - other values will be converted to their string representation using {@see QueryBuilderInterface::buildValue()}. | ||
| */ | ||
| public function __construct( | ||
| public readonly mixed $condition, | ||
| public readonly mixed $result, | ||
| ) { | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.