Skip to content

Commit 5cf51e9

Browse files
authored
Implement CaseExpressionBuilder class (#415)
1 parent 68ef774 commit 5cf51e9

File tree

6 files changed

+139
-1
lines changed

6 files changed

+139
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
- New #408, #410: Implement `DMLQueryBuilder::upsertReturning()` method (@Tigrov)
4646
- Enh #412: Reduce binding parameters (@Tigrov)
4747
- Chg #414: Rename `DMLQueryBuilder::insertWithReturningPks()` to `DMLQueryBuilder::insertReturningPks()` (@Tigrov)
48+
- Enh #415: Implement `CaseExpressionBuilder` class (@Tigrov)
4849

4950
## 1.3.0 March 21, 2024
5051

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Db\Pgsql\Builder;
6+
7+
use InvalidArgumentException;
8+
use Yiisoft\Db\Expression\CaseExpression;
9+
use Yiisoft\Db\Expression\ExpressionInterface;
10+
use Yiisoft\Db\Schema\Column\ColumnInterface;
11+
12+
use function is_string;
13+
14+
/**
15+
* Builds expressions for {@see CaseExpression}.
16+
*/
17+
final class CaseExpressionBuilder extends \Yiisoft\Db\Expression\CaseExpressionBuilder
18+
{
19+
public function build(ExpressionInterface $expression, array &$params = []): string
20+
{
21+
$whenClauses = $expression->getWhen();
22+
23+
if (empty($whenClauses)) {
24+
throw new InvalidArgumentException('The CASE expression must have at least one WHEN clause.');
25+
}
26+
27+
$sql = 'CASE';
28+
29+
$case = $expression->getCase();
30+
31+
if ($case !== null) {
32+
$caseTypeHint = $this->buildTypeHint($expression->getCaseType());
33+
$sql .= ' ' . $this->buildConditionWithTypeHint($case, $caseTypeHint, $params);
34+
} else {
35+
$caseTypeHint = '';
36+
}
37+
38+
foreach ($whenClauses as $when) {
39+
$sql .= ' WHEN ' . $this->buildConditionWithTypeHint($when->condition, $caseTypeHint, $params);
40+
$sql .= ' THEN ' . $this->buildResult($when->result, $params);
41+
}
42+
43+
if ($expression->hasElse()) {
44+
$sql .= ' ELSE ' . $this->buildResult($expression->getElse(), $params);
45+
}
46+
47+
return $sql . ' END';
48+
}
49+
50+
private function buildConditionWithTypeHint(mixed $condition, string $typeHint, array &$params): string
51+
{
52+
$builtCondition = $this->buildCondition($condition, $params);
53+
54+
return $typeHint !== '' ? "($builtCondition)$typeHint" : $builtCondition;
55+
}
56+
57+
private function buildTypeHint(string|ColumnInterface $type): string
58+
{
59+
if (is_string($type)) {
60+
return $type === '' ? '' : "::$type";
61+
}
62+
63+
return '::' . $this->queryBuilder->getColumnDefinitionBuilder()->buildType($type);
64+
}
65+
}

src/DQLQueryBuilder.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
namespace Yiisoft\Db\Pgsql;
66

77
use Yiisoft\Db\Expression\ArrayExpression;
8+
use Yiisoft\Db\Expression\CaseExpression;
89
use Yiisoft\Db\Expression\JsonExpression;
910
use Yiisoft\Db\Expression\StructuredExpression;
1011
use Yiisoft\Db\Pgsql\Builder\ArrayExpressionBuilder;
1112
use Yiisoft\Db\Pgsql\Builder\ArrayOverlapsConditionBuilder;
13+
use Yiisoft\Db\Pgsql\Builder\CaseExpressionBuilder;
1214
use Yiisoft\Db\Pgsql\Builder\JsonOverlapsConditionBuilder;
1315
use Yiisoft\Db\Pgsql\Builder\LikeConditionBuilder;
1416
use Yiisoft\Db\Pgsql\Builder\StructuredExpressionBuilder;
@@ -51,6 +53,7 @@ protected function defaultExpressionBuilders(): array
5153
JsonOverlapsCondition::class => JsonOverlapsConditionBuilder::class,
5254
StructuredExpression::class => StructuredExpressionBuilder::class,
5355
LikeCondition::class => LikeConditionBuilder::class,
56+
CaseExpression::class => CaseExpressionBuilder::class,
5457
];
5558
}
5659
}

tests/Provider/QueryBuilderProvider.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
use Yiisoft\Db\Constant\DataType;
99
use Yiisoft\Db\Constant\PseudoType;
1010
use Yiisoft\Db\Expression\ArrayExpression;
11+
use Yiisoft\Db\Expression\CaseExpression;
1112
use Yiisoft\Db\Expression\Expression;
1213
use Yiisoft\Db\Pgsql\Column\ColumnBuilder;
14+
use Yiisoft\Db\Pgsql\Column\IntegerColumn;
1315
use Yiisoft\Db\Pgsql\Tests\Support\TestTrait;
1416
use Yiisoft\Db\Query\Query;
1517

@@ -511,4 +513,60 @@ public static function prepareValue(): array
511513

512514
return $values;
513515
}
516+
517+
public static function caseExpressionBuilder(): array
518+
{
519+
$data = parent::caseExpressionBuilder();
520+
521+
$db = self::getDb();
522+
$serverVersion = $db->getServerInfo()->getVersion();
523+
$db->close();
524+
525+
if (version_compare($serverVersion, '10', '<')) {
526+
$data['without case expression'] = [
527+
(new CaseExpression())
528+
->addWhen(['=', 'column_name', 1], $paramA = new Param('a', DataType::STRING))
529+
->addWhen(
530+
'"column_name" = 2',
531+
$db->select(new Expression(
532+
':pv2::text',
533+
[':pv2' => $paramB = new Param('b', DataType::STRING)],
534+
)),
535+
),
536+
'CASE WHEN "column_name" = :qp0 THEN :qp1 WHEN "column_name" = 2 THEN (SELECT :pv2::text) END',
537+
[':qp0' => 1, ':qp1' => $paramA, ':pv2' => $paramB],
538+
'b',
539+
];
540+
}
541+
542+
return [
543+
...$data,
544+
'without case and type hint' => [
545+
(new CaseExpression())->caseType('int')
546+
->addWhen(true, "'a'"),
547+
"CASE WHEN TRUE THEN 'a' END",
548+
[],
549+
'a',
550+
],
551+
'with case and type hint' => [
552+
(new CaseExpression('1 + 1', 'int'))
553+
->addWhen(1, "'a'")
554+
->else("'b'"),
555+
"CASE (1 + 1)::int WHEN (1)::int THEN 'a' ELSE 'b' END",
556+
[],
557+
'b',
558+
],
559+
'with case and type hint with column' => [
560+
(new CaseExpression('1 + 1', new IntegerColumn()))
561+
->addWhen(1, $paramA = new Param('a', DataType::STRING))
562+
->else($paramB = new Param('b', DataType::STRING)),
563+
'CASE (1 + 1)::integer WHEN (1)::integer THEN :qp0 ELSE :qp1 END',
564+
[
565+
':qp0' => $paramA,
566+
':qp1' => $paramB,
567+
],
568+
'b',
569+
],
570+
];
571+
}
514572
}

tests/QueryBuilderTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Yiisoft\Db\Driver\Pdo\PdoConnectionInterface;
99
use Yiisoft\Db\Exception\IntegrityException;
1010
use Yiisoft\Db\Exception\NotSupportedException;
11+
use Yiisoft\Db\Expression\CaseExpression;
1112
use Yiisoft\Db\Expression\Expression;
1213
use Yiisoft\Db\Expression\ExpressionInterface;
1314
use Yiisoft\Db\Pgsql\Tests\Provider\QueryBuilderProvider;
@@ -576,4 +577,14 @@ public function testPrepareValue(string $expected, mixed $value): void
576577
{
577578
parent::testPrepareValue($expected, $value);
578579
}
580+
581+
#[DataProviderExternal(QueryBuilderProvider::class, 'caseExpressionBuilder')]
582+
public function testCaseExpressionBuilder(
583+
CaseExpression $case,
584+
string $expectedSql,
585+
array $expectedParams,
586+
string|int $expectedResult,
587+
): void {
588+
parent::testCaseExpressionBuilder($case, $expectedSql, $expectedParams, $expectedResult);
589+
}
579590
}

tests/Support/TestTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ protected function getDsn(): string
6060
return $this->dsn;
6161
}
6262

63-
protected function getDriverName(): string
63+
protected static function getDriverName(): string
6464
{
6565
return 'pgsql';
6666
}

0 commit comments

Comments
 (0)