Skip to content

Commit 4cdc638

Browse files
feat: add support for DISTINCT and ORDER BY clauses to json_agg() and jsonb_agg() (#317)
1 parent 3c46021 commit 4cdc638

File tree

6 files changed

+89
-48
lines changed

6 files changed

+89
-48
lines changed

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ArrayAgg.php

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,6 @@
44

55
namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
66

7-
use Doctrine\ORM\Query\Lexer;
8-
use Doctrine\ORM\Query\Parser;
9-
use Doctrine\ORM\Query\SqlWalker;
10-
use Doctrine\ORM\Query\TokenType;
11-
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Traits\DistinctableTrait;
12-
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Traits\OrderableTrait;
13-
use MartinGeorgiev\Utils\DoctrineOrm;
14-
157
/**
168
* Implementation of PostgreSQL ARRAY_AGG().
179
*
@@ -20,42 +12,11 @@
2012
*
2113
* @author Martin Georgiev <martin.georgiev@gmail.com>
2214
*/
23-
class ArrayAgg extends BaseFunction
15+
class ArrayAgg extends BaseAggregateFunction
2416
{
25-
use OrderableTrait;
26-
use DistinctableTrait;
27-
2817
protected function customizeFunction(): void
2918
{
3019
$this->setFunctionPrototype('array_agg(%s%s%s)');
3120
$this->addNodeMapping('StringPrimary');
3221
}
33-
34-
public function parse(Parser $parser): void
35-
{
36-
$shouldUseLexer = DoctrineOrm::isPre219();
37-
38-
$this->customizeFunction();
39-
40-
$parser->match($shouldUseLexer ? Lexer::T_IDENTIFIER : TokenType::T_IDENTIFIER);
41-
$parser->match($shouldUseLexer ? Lexer::T_OPEN_PARENTHESIS : TokenType::T_OPEN_PARENTHESIS);
42-
43-
$this->parseDistinctClause($parser);
44-
$this->expression = $parser->StringPrimary();
45-
46-
$this->parseOrderByClause($parser);
47-
48-
$parser->match($shouldUseLexer ? Lexer::T_CLOSE_PARENTHESIS : TokenType::T_CLOSE_PARENTHESIS);
49-
}
50-
51-
public function getSql(SqlWalker $sqlWalker): string
52-
{
53-
$dispatched = [
54-
$this->getOptionalDistinctClause(),
55-
$this->expression->dispatch($sqlWalker),
56-
$this->getOptionalOrderByClause($sqlWalker),
57-
];
58-
59-
return \vsprintf($this->functionPrototype, $dispatched);
60-
}
6122
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
use Doctrine\ORM\Query\Lexer;
8+
use Doctrine\ORM\Query\Parser;
9+
use Doctrine\ORM\Query\SqlWalker;
10+
use Doctrine\ORM\Query\TokenType;
11+
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Traits\DistinctableTrait;
12+
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Traits\OrderableTrait;
13+
use MartinGeorgiev\Utils\DoctrineOrm;
14+
15+
/**
16+
* @since 3.0
17+
*
18+
* @author Martin Georgiev <martin.georgiev@gmail.com>
19+
*/
20+
abstract class BaseAggregateFunction extends BaseFunction
21+
{
22+
use OrderableTrait;
23+
use DistinctableTrait;
24+
25+
public function parse(Parser $parser): void
26+
{
27+
$shouldUseLexer = DoctrineOrm::isPre219();
28+
29+
$this->customizeFunction();
30+
31+
$parser->match($shouldUseLexer ? Lexer::T_IDENTIFIER : TokenType::T_IDENTIFIER);
32+
$parser->match($shouldUseLexer ? Lexer::T_OPEN_PARENTHESIS : TokenType::T_OPEN_PARENTHESIS);
33+
34+
$this->parseDistinctClause($parser);
35+
$this->expression = $parser->StringPrimary();
36+
37+
$this->parseOrderByClause($parser);
38+
39+
$parser->match($shouldUseLexer ? Lexer::T_CLOSE_PARENTHESIS : TokenType::T_CLOSE_PARENTHESIS);
40+
}
41+
42+
public function getSql(SqlWalker $sqlWalker): string
43+
{
44+
$dispatched = [
45+
$this->getOptionalDistinctClause(),
46+
$this->expression->dispatch($sqlWalker),
47+
$this->getOptionalOrderByClause($sqlWalker),
48+
];
49+
50+
return \vsprintf($this->functionPrototype, $dispatched);
51+
}
52+
}

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonAgg.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
*
1313
* @author Martin Georgiev <martin.georgiev@gmail.com>
1414
*/
15-
class JsonAgg extends BaseFunction
15+
class JsonAgg extends BaseAggregateFunction
1616
{
1717
protected function customizeFunction(): void
1818
{
19-
$this->setFunctionPrototype('json_agg(%s)');
19+
$this->setFunctionPrototype('json_agg(%s%s%s)');
2020
$this->addNodeMapping('StringPrimary');
2121
}
2222
}

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbAgg.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
*
1313
* @author Martin Georgiev <martin.georgiev@gmail.com>
1414
*/
15-
class JsonbAgg extends BaseFunction
15+
class JsonbAgg extends BaseAggregateFunction
1616
{
1717
protected function customizeFunction(): void
1818
{
19-
$this->setFunctionPrototype('jsonb_agg(%s)');
19+
$this->setFunctionPrototype('jsonb_agg(%s%s%s)');
2020
$this->addNodeMapping('StringPrimary');
2121
}
2222
}

tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonAggTest.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,28 @@ protected function getStringFunctions(): array
1919
protected function getExpectedSqlStatements(): array
2020
{
2121
return [
22-
'SELECT json_agg(c0_.text1) AS sclr_0 FROM ContainsTexts c0_',
22+
'basic usage' => 'SELECT json_agg(c0_.text1) AS sclr_0 FROM ContainsTexts c0_',
23+
'with concatenation' => 'SELECT json_agg(c0_.text1 || c0_.text2) AS sclr_0 FROM ContainsTexts c0_',
24+
'with DISTINCT' => 'SELECT json_agg(DISTINCT c0_.text1) AS sclr_0 FROM ContainsTexts c0_',
25+
'with DISTINCT and concatenation' => 'SELECT json_agg(DISTINCT c0_.text1 || c0_.text2) AS sclr_0 FROM ContainsTexts c0_',
26+
'with ORDER BY' => 'SELECT json_agg(c0_.text1 ORDER BY c0_.text1 ASC) AS sclr_0 FROM ContainsTexts c0_',
27+
'with ORDER BY DESC' => 'SELECT json_agg(c0_.text1 ORDER BY c0_.text1 DESC) AS sclr_0 FROM ContainsTexts c0_',
28+
'with DISTINCT and ORDER BY' => 'SELECT json_agg(DISTINCT c0_.text1 ORDER BY c0_.text1 ASC) AS sclr_0 FROM ContainsTexts c0_',
29+
'with DISTINCT and ORDER BY DESC' => 'SELECT json_agg(DISTINCT c0_.text1 ORDER BY c0_.text1 DESC) AS sclr_0 FROM ContainsTexts c0_',
2330
];
2431
}
2532

2633
protected function getDqlStatements(): array
2734
{
2835
return [
29-
\sprintf('SELECT JSON_AGG(e.text1) FROM %s e', ContainsTexts::class),
36+
'basic usage' => \sprintf('SELECT JSON_AGG(e.text1) FROM %s e', ContainsTexts::class),
37+
'with concatenation' => \sprintf('SELECT JSON_AGG(CONCAT(e.text1, e.text2)) FROM %s e', ContainsTexts::class),
38+
'with DISTINCT' => \sprintf('SELECT JSON_AGG(DISTINCT e.text1) FROM %s e', ContainsTexts::class),
39+
'with DISTINCT and concatenation' => \sprintf('SELECT JSON_AGG(DISTINCT CONCAT(e.text1, e.text2)) FROM %s e', ContainsTexts::class),
40+
'with ORDER BY' => \sprintf('SELECT JSON_AGG(e.text1 ORDER BY e.text1) FROM %s e', ContainsTexts::class),
41+
'with ORDER BY DESC' => \sprintf('SELECT JSON_AGG(e.text1 ORDER BY e.text1 DESC) FROM %s e', ContainsTexts::class),
42+
'with DISTINCT and ORDER BY' => \sprintf('SELECT JSON_AGG(DISTINCT e.text1 ORDER BY e.text1) FROM %s e', ContainsTexts::class),
43+
'with DISTINCT and ORDER BY DESC' => \sprintf('SELECT JSON_AGG(DISTINCT e.text1 ORDER BY e.text1 DESC) FROM %s e', ContainsTexts::class),
3044
];
3145
}
3246
}

tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbAggTest.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,28 @@ protected function getStringFunctions(): array
1919
protected function getExpectedSqlStatements(): array
2020
{
2121
return [
22-
'SELECT jsonb_agg(c0_.text1) AS sclr_0 FROM ContainsTexts c0_',
22+
'basic usage' => 'SELECT jsonb_agg(c0_.text1) AS sclr_0 FROM ContainsTexts c0_',
23+
'with concatenation' => 'SELECT jsonb_agg(c0_.text1 || c0_.text2) AS sclr_0 FROM ContainsTexts c0_',
24+
'with DISTINCT' => 'SELECT jsonb_agg(DISTINCT c0_.text1) AS sclr_0 FROM ContainsTexts c0_',
25+
'with DISTINCT and concatenation' => 'SELECT jsonb_agg(DISTINCT c0_.text1 || c0_.text2) AS sclr_0 FROM ContainsTexts c0_',
26+
'with ORDER BY' => 'SELECT jsonb_agg(c0_.text1 ORDER BY c0_.text1 ASC) AS sclr_0 FROM ContainsTexts c0_',
27+
'with ORDER BY DESC' => 'SELECT jsonb_agg(c0_.text1 ORDER BY c0_.text1 DESC) AS sclr_0 FROM ContainsTexts c0_',
28+
'with DISTINCT and ORDER BY' => 'SELECT jsonb_agg(DISTINCT c0_.text1 ORDER BY c0_.text1 ASC) AS sclr_0 FROM ContainsTexts c0_',
29+
'with DISTINCT and ORDER BY DESC' => 'SELECT jsonb_agg(DISTINCT c0_.text1 ORDER BY c0_.text1 DESC) AS sclr_0 FROM ContainsTexts c0_',
2330
];
2431
}
2532

2633
protected function getDqlStatements(): array
2734
{
2835
return [
29-
\sprintf('SELECT JSONB_AGG(e.text1) FROM %s e', ContainsTexts::class),
36+
'basic usage' => \sprintf('SELECT JSONB_AGG(e.text1) FROM %s e', ContainsTexts::class),
37+
'with concatenation' => \sprintf('SELECT JSONB_AGG(CONCAT(e.text1, e.text2)) FROM %s e', ContainsTexts::class),
38+
'with DISTINCT' => \sprintf('SELECT JSONB_AGG(DISTINCT e.text1) FROM %s e', ContainsTexts::class),
39+
'with DISTINCT and concatenation' => \sprintf('SELECT JSONB_AGG(DISTINCT CONCAT(e.text1, e.text2)) FROM %s e', ContainsTexts::class),
40+
'with ORDER BY' => \sprintf('SELECT JSONB_AGG(e.text1 ORDER BY e.text1) FROM %s e', ContainsTexts::class),
41+
'with ORDER BY DESC' => \sprintf('SELECT JSONB_AGG(e.text1 ORDER BY e.text1 DESC) FROM %s e', ContainsTexts::class),
42+
'with DISTINCT and ORDER BY' => \sprintf('SELECT JSONB_AGG(DISTINCT e.text1 ORDER BY e.text1) FROM %s e', ContainsTexts::class),
43+
'with DISTINCT and ORDER BY DESC' => \sprintf('SELECT JSONB_AGG(DISTINCT e.text1 ORDER BY e.text1 DESC) FROM %s e', ContainsTexts::class),
3044
];
3145
}
3246
}

0 commit comments

Comments
 (0)