Skip to content

Commit

Permalink
feat: query builder adapter now supports row values method
Browse files Browse the repository at this point in the history
  • Loading branch information
priyadi committed Jul 27, 2024
1 parent cf05b01 commit 626f5f9
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* refactor: simplify `KeysetExpressionCalculator`
* chore: cleanup
* feat: native query adapter now supports row values method
* feat: query builder adapter now supports row values method

# 0.15.1

Expand Down
61 changes: 49 additions & 12 deletions packages/rekapager-doctrine-orm-adapter/src/QueryBuilderAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\Query\Expr\From;
use Doctrine\ORM\Query\Expr\OrderBy;
use Doctrine\ORM\QueryBuilder;
Expand All @@ -27,6 +28,7 @@
use Rekalogika\Rekapager\Adapter\Common\Field;
use Rekalogika\Rekapager\Adapter\Common\IndexResolver;
use Rekalogika\Rekapager\Adapter\Common\KeysetExpressionCalculator;
use Rekalogika\Rekapager\Adapter\Common\SeekMethod;
use Rekalogika\Rekapager\Doctrine\ORM\Exception\UnsupportedQueryBuilderException;
use Rekalogika\Rekapager\Doctrine\ORM\Internal\KeysetQueryBuilderVisitor;
use Rekalogika\Rekapager\Doctrine\ORM\Internal\QueryBuilderKeysetItem;
Expand Down Expand Up @@ -61,6 +63,7 @@ public function __construct(
private readonly array $typeMapping = [],
private readonly bool|null $useOutputWalkers = null,
private readonly string|null $indexBy = null,
private readonly SeekMethod $seekMethod = SeekMethod::Approximated,
) {
if ($queryBuilder->getFirstResult() !== 0 || $queryBuilder->getMaxResults() !== null) {
throw new UnsupportedQueryBuilderException();
Expand Down Expand Up @@ -139,20 +142,14 @@ private function getQueryBuilder(

// returns early if there are no boundary values

$fields = $this->createCalculatorFields($boundaryValues, $orderings);

if ($fields === []) {
return $queryBuilder;
}
[$where, $parameters] = $this->generateWhereExpression(
boundaryValues: $boundaryValues,
orderings: $orderings
);

// adds where expression to the querybuilder

$keysetExpression = KeysetExpressionCalculator::calculate($fields);

$visitor = new KeysetQueryBuilderVisitor();
$queryBuilder->andWhere($visitor->dispatch($keysetExpression));
$queryBuilder->andWhere($where);

foreach ($visitor->getParameters() as $template => $parameter) {
foreach ($parameters as $template => $parameter) {
$queryBuilder->setParameter(
$template,
$parameter->getValue(),
Expand All @@ -163,6 +160,46 @@ private function getQueryBuilder(
return $queryBuilder;
}

/**
* @param array<string,QueryParameter> $boundaryValues Key is the property name, value is the bound value. Null if unbounded.
* @param non-empty-array<string,'ASC'|'DESC'> $orderings
* @return array{?Andx,array<string,QueryParameter>}
*/
private function generateWhereExpression(
array $boundaryValues,
array $orderings,
): array {
$fields = $this->createCalculatorFields($boundaryValues, $orderings);

if ($fields === []) {
return [null, []];
}

return match ($this->seekMethod) {
SeekMethod::Approximated => $this->generateApproximatedWhereExpression($fields),
// SeekMethod::RowValues => $this->generateRowValuesWhereExpression($fields),
// SeekMethod::Auto => $this->generateAutoWhereExpression($fields),
default => throw new LogicException('Unsupported seek method'),
};
}

/**
* @param non-empty-list<Field> $fields
* @return array{?Andx,array<string,QueryParameter>}
*/
private function generateApproximatedWhereExpression(array $fields): array
{
$expression = KeysetExpressionCalculator::calculate($fields);

$visitor = new KeysetQueryBuilderVisitor();
$where = $visitor->dispatch($expression);
assert($where instanceof Andx);

$parameters = $visitor->getParameters();

return [$where, $parameters];
}

/**
* @param array<string,QueryParameter> $boundaryValues
* @param non-empty-array<string, 'ASC'|'DESC'> $orderings
Expand Down
53 changes: 53 additions & 0 deletions packages/rekapager-doctrine-orm-adapter/src/RowValuesFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/rekapager package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Rekapager\Doctrine\ORM;

use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;

class RowValuesFunction extends FunctionNode
{
/** @var list<Node|string> */
public array $values = [];

public function getSql(SqlWalker $sqlWalker): string
{
$queryBuilder = [];

foreach ($this->values as $value) {
$queryBuilder[] = $sqlWalker->walkArithmeticPrimary($value);
}

return '(' . implode(', ', $queryBuilder) . ')';
}

public function parse(Parser $parser): void
{
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->values[] = $parser->ArithmeticPrimary();
$parser->match(TokenType::T_COMMA);
$this->values[] = $parser->ArithmeticPrimary();

while ($parser->getLexer()->isNextToken(TokenType::T_COMMA)) {
$parser->match(TokenType::T_COMMA);
$this->values[] = $parser->ArithmeticPrimary();
}

$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}
3 changes: 3 additions & 0 deletions tests/config/packages/doctrine.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ doctrine:
dir: "%kernel.project_dir%/src/App/Entity"
prefix: 'Rekalogika\Rekapager\Tests\App\Entity'
alias: App
dql:
string_functions:
rekapager_row_values: Rekalogika\Rekapager\Doctrine\ORM\RowValuesFunction

0 comments on commit 626f5f9

Please sign in to comment.