Skip to content

Commit

Permalink
Merge pull request #3 from pfilsx/dev
Browse files Browse the repository at this point in the history
add aggregate functions with filter
  • Loading branch information
pfilsx authored Mar 14, 2023
2 parents 97d0cf2 + d204544 commit be3b1b2
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Documentation
-------------

* [ENUMS](docs/ENUMS.md)
* [Functions](docs/FUNCTIONS-AND-OPERATORS.md)

License
-------
Expand Down
23 changes: 23 additions & 0 deletions docs/FUNCTIONS-AND-OPERATORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Available functions
===================

| PostgreSQL function | DQL function | Implementation |
|---------------------|--------------|-------------------------------------------------------------------------------------------------------------|
| ARRAY_AGG() | ARRAY_AGG | [Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions\ArrayAgg](../src/ORM/Query/AST/Functions/ArrayAgg.php) |
| JSON_AGG() | JSON_AGG | [Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions\JsonAgg](../src/ORM/Query/AST/Functions/JsonAgg.php) |
| JSONB_AGG() | JSONB_AGG | [Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions\JsonbAgg](../src/ORM/Query/AST/Functions/JsonbAgg.php) |
| STRING_AGG() | STRING_AGG | [Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions\StringAgg](../src/ORM/Query/AST/Functions/StringAgg.php) |

Available operators
===================

| PostgreSQL operator | DQL function | Implementation |
|---------------------|-------------------------|---------------------------------------------------------------------------------------------------------------------------------|
| || | JSONB_CONCAT | [Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions\JsonbConcat](../src/ORM/Query/AST/Functions/JsonbConcat.php) |
| @> | JSONB_CONTAINS | [Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions\JsonbContains](../src/ORM/Query/AST/Functions/JsonbContains.php) |
| ? | JSONB_KEY_EXISTS | [Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions\JsonbKeyExists](../src/ORM/Query/AST/Functions/JsonbKeyExists.php) |
| - | JSONB_REMOVE | [Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions\JsonbRemove](../src/ORM/Query/AST/Functions/JsonbRemove.php) |
| -> | JSON_GET_FIELD | [Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions\JsonGetField](../src/ORM/Query/AST/Functions/JsonGetField.php) |
| ->> | JSON_GET_FIELD_AS_TEXT | [Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions\JsonGetFieldAsText](../src/ORM/Query/AST/Functions/JsonGetFieldAsText.php) |
| #> | JSON_GET_OBJECT | [Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions\JsonGetObject](../src/ORM/Query/AST/Functions/JsonGetObject.php) |
| #>> | JSON_GET_OBJECT_AS_TEXT | [Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions\JsonGetObjectAsText](../src/ORM/Query/AST/Functions/JsonGetObjectAsText.php) |
23 changes: 23 additions & 0 deletions src/ORM/Query/AST/FilterExpression.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Pfilsx\PostgreSQLDoctrine\ORM\Query\AST;

use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\AST\WhereClause;

final class FilterExpression extends Node
{
private WhereClause $whereClause;

public function __construct(WhereClause $whereClause)
{
$this->whereClause = $whereClause;
}

public function dispatch($walker): string
{
return "FILTER ({$this->whereClause->dispatch($walker)})";
}
}
56 changes: 56 additions & 0 deletions src/ORM/Query/AST/Functions/AggregateWithFilterFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions;

use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\FilterExpression;

abstract class AggregateWithFilterFunction extends FunctionNode
{
private const FILTER_IDENTIFIER = 'FILTER';
private ?FilterExpression $filterExpression = null;

public function parse(Parser $parser): void
{
$this->parseFunction($parser);

$lexer = $parser->getLexer();

if (!$lexer->isNextToken(Lexer::T_IDENTIFIER)) {
return;
}

$lookaheadValue = $lexer->lookahead['value'] ?? null;

if (!is_string($lookaheadValue) || mb_strtoupper($lookaheadValue) !== self::FILTER_IDENTIFIER) {
return;
}

$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);

$this->filterExpression = new FilterExpression($parser->WhereClause());

$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}

abstract public function parseFunction(Parser $parser): void;

public function getSql(SqlWalker $sqlWalker): string
{
$sql = $this->getFunctionSql($sqlWalker);

if ($this->filterExpression !== null) {
$sql .= " {$this->filterExpression->dispatch($sqlWalker)}";
}

return $sql;
}

abstract public function getFunctionSql(SqlWalker $sqlWalker): string;
}
36 changes: 36 additions & 0 deletions src/ORM/Query/AST/Functions/ArrayAgg.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions;

use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;

/**
* Implementation of PostgreSql ARRAY_AGG().
*
* @see https://www.postgresql.org/docs/current/functions-aggregate.html#FUNCTIONS-AGGREGATE-TABLE
*
* @example ARRAY_AGG(entity.field)
* @example ARRAY_AGG(entity.field) FILTER (WHERE entity.field IS NOT NULL)
*/
final class ArrayAgg extends AggregateWithFilterFunction
{
private Node $expr;

public function parseFunction(Parser $parser): void
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->expr = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}

public function getFunctionSql(SqlWalker $sqlWalker): string
{
return "ARRAY_AGG({$this->expr->dispatch($sqlWalker)})";
}
}
36 changes: 36 additions & 0 deletions src/ORM/Query/AST/Functions/JsonAgg.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions;

use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;

/**
* Implementation of PostgreSql JSON_AGG().
*
* @see https://www.postgresql.org/docs/current/functions-aggregate.html#FUNCTIONS-AGGREGATE-TABLE
*
* @example JSON_AGG(entity.field)
* @example JSON_AGG(entity.field) FILTER (WHERE entity.field IS NOT NULL)
*/
final class JsonAgg extends AggregateWithFilterFunction
{
private Node $expr;

public function parseFunction(Parser $parser): void
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->expr = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}

public function getFunctionSql(SqlWalker $sqlWalker): string
{
return "JSON_AGG({$this->expr->dispatch($sqlWalker)})";
}
}
36 changes: 36 additions & 0 deletions src/ORM/Query/AST/Functions/JsonbAgg.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions;

use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;

/**
* Implementation of PostgreSql JSONB_AGG().
*
* @see https://www.postgresql.org/docs/current/functions-aggregate.html#FUNCTIONS-AGGREGATE-TABLE
*
* @example JSONB_AGG(entity.field)
* @example JSONB_AGG(entity.field) FILTER (WHERE entity.field IS NOT NULL)
*/
final class JsonbAgg extends AggregateWithFilterFunction
{
private Node $expr;

public function parseFunction(Parser $parser): void
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->expr = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}

public function getFunctionSql(SqlWalker $sqlWalker): string
{
return "JSON_AGG({$this->expr->dispatch($sqlWalker)})";
}
}
52 changes: 52 additions & 0 deletions src/ORM/Query/AST/Functions/StringAgg.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Pfilsx\PostgreSQLDoctrine\ORM\Query\AST\Functions;

use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;

/**
* Implementation of PostgreSql STRING_AGG().
*
* @see https://www.postgresql.org/docs/current/functions-aggregate.html#FUNCTIONS-AGGREGATE-TABLE
*
* @example STRING_AGG(entity.field, ', ')
* @example STRING_AGG(entity.field, ', ') FILTER (WHERE entity.field IS NOT NULL)
*/
final class StringAgg extends AggregateWithFilterFunction
{
private bool $distinct = false;
private Node $expr;
private Node $delimiter;

public function parseFunction(Parser $parser): void
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);

$lexer = $parser->getLexer();
if ($lexer->isNextToken(Lexer::T_DISTINCT)) {
$parser->match(Lexer::T_DISTINCT);
$this->distinct = true;
}

$this->expr = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->delimiter = $parser->StringPrimary();

$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}

public function getFunctionSql(SqlWalker $sqlWalker): string
{
return sprintf('STRING_AGG(%s%s, %s)',
$this->distinct ? 'DISTINCT' : '',
$this->expr->dispatch($sqlWalker),
$this->delimiter->dispatch($sqlWalker)
);
}
}

0 comments on commit be3b1b2

Please sign in to comment.