Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
hrach committed Oct 30, 2024
1 parent 42e52f4 commit ee8b661
Show file tree
Hide file tree
Showing 33 changed files with 271 additions and 152 deletions.
2 changes: 1 addition & 1 deletion src/Collection/Aggregations/AnyAggregator.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function aggregateExpression(
ExpressionContext $context,
): DbalExpressionResult
{
if ($context !== ExpressionContext::FilterOr) {
if ($context !== ExpressionContext::FilterOrWithHavingClause) {
// When we are not in OR expression, we may simply filter the joined table by the condition.
// Otherwise, we have to employ a HAVING clause with aggregation function.
return $expression;
Expand Down
11 changes: 7 additions & 4 deletions src/Collection/DbalCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,13 @@ public function orderBy($expression, string $direction = ICollection::ASC): ICol

foreach ($expression as $subExpression => $subDirection) {
$collection->ordering[] = [
$helper->processExpression($collection->queryBuilder, $subExpression, ExpressionContext::FilterAnd, null),
$helper->processExpression($collection->queryBuilder, $subExpression, null),
$subDirection,
];
}
} else {
$collection->ordering[] = [
$helper->processExpression($collection->queryBuilder, $expression, ExpressionContext::ValueExpression, null),
$helper->processExpression($collection->queryBuilder, $expression, null),
$direction,
];
}
Expand Down Expand Up @@ -294,15 +294,18 @@ public function getQueryBuilder(): QueryBuilder
$expression = $helper->processExpression(
builder: $this->queryBuilder,
expression: $args,
context: ExpressionContext::FilterAnd,
aggregator: null,
);
$finalContext = $expression->havingExpression === null
? ExpressionContext::FilterAnd
: ExpressionContext::FilterAndWithHavingClause;
$expression = $expression->collect($finalContext);
$joins = $expression->joins;
$groupBy = $expression->groupBy;
if ($expression->expression !== null) {
$this->queryBuilder->andWhere($expression->expression, ...$expression->args);
}
if ($expression->havingExpression !== null) {
if ($expression->havingExpression !== null && $expression->havingArgs !== []) {
$this->queryBuilder->andHaving($expression->havingExpression, ...$expression->havingArgs);
}
if ($this->mapper->getDatabasePlatform()->getName() === MySqlPlatform::NAME) {
Expand Down
2 changes: 2 additions & 0 deletions src/Collection/Expression/ExpressionContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ enum ExpressionContext
{
case FilterAnd;
case FilterOr;
case FilterAndWithHavingClause;
case FilterOrWithHavingClause;
case ValueExpression;
}
10 changes: 8 additions & 2 deletions src/Collection/Functions/BaseCompareFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ public function processArrayExpression(
}

if ($valueReference->aggregator !== null) {
if ($targetValue === null && $valueReference->value === [] && $this instanceof CompareEqualsFunction) {
return new ArrayExpressionResult(
value: [true],
aggregator: $valueReference->aggregator,
propertyMetadata: null,
);
}
$values = array_map(
function ($value) use ($targetValue): bool {
return $this->evaluateInPhp($value, $targetValue);
Expand All @@ -59,13 +66,12 @@ public function processDbalExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
ExpressionContext $context,
?Aggregator $aggregator = null,
): DbalExpressionResult
{
assert(count($args) === 2);

$expression = $helper->processExpression($builder, $args[0], $context, $aggregator);
$expression = $helper->processExpression($builder, $args[0], $aggregator);

if ($expression->valueNormalizer !== null) {
$cb = $expression->valueNormalizer;
Expand Down
5 changes: 2 additions & 3 deletions src/Collection/Functions/BaseNumericAggregateFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ public function processDbalExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
ExpressionContext $context,
?Aggregator $aggregator = null,
): DbalExpressionResult
{
Expand All @@ -64,7 +63,7 @@ public function processDbalExpression(
throw new InvalidStateException("Cannot apply two aggregations simultaneously.");
}

return $helper->processExpression($builder, $args[0], $context, $this->aggregator)
->applyAggregator($builder, ExpressionContext::ValueExpression);
return $helper->processExpression($builder, $args[0], $this->aggregator)
->applyAggregator(ExpressionContext::ValueExpression,);
}
}
1 change: 0 additions & 1 deletion src/Collection/Functions/CollectionFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ public function processDbalExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
ExpressionContext $context,
?Aggregator $aggregator = null,
): DbalExpressionResult;
}
3 changes: 1 addition & 2 deletions src/Collection/Functions/CompareLikeFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,12 @@ public function processDbalExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
ExpressionContext $context,
?Aggregator $aggregator = null,
): DbalExpressionResult
{
assert(count($args) === 2);

$expression = $helper->processExpression($builder, $args[0], $context, $aggregator);
$expression = $helper->processExpression($builder, $args[0], $aggregator);

$likeExpression = $args[1];
assert($likeExpression instanceof LikeExpression);
Expand Down
2 changes: 0 additions & 2 deletions src/Collection/Functions/ConjunctionOperatorFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ public function processDbalExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
ExpressionContext $context,
?Aggregator $aggregator = null,
): DbalExpressionResult
{
Expand All @@ -112,7 +111,6 @@ public function processDbalExpression(
helper: $helper,
builder: $builder,
args: $args,
context: $context,
aggregator: $aggregator,
);
}
Expand Down
2 changes: 0 additions & 2 deletions src/Collection/Functions/DisjunctionOperatorFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ public function processDbalExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
ExpressionContext $context,
?Aggregator $aggregator = null,
): DbalExpressionResult
{
Expand All @@ -105,7 +104,6 @@ public function processDbalExpression(
helper: $helper,
builder: $builder,
args: $args,
context: ExpressionContext::FilterOr,
aggregator: $aggregator,
);
}
Expand Down
1 change: 0 additions & 1 deletion src/Collection/Functions/FetchPropertyFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ public function processDbalExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
ExpressionContext $context,
?Aggregator $aggregator = null,
): DbalExpressionResult
{
Expand Down
99 changes: 58 additions & 41 deletions src/Collection/Functions/JunctionFunctionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,59 +61,76 @@ protected function processQueryBuilderExpressionWithModifier(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args,
ExpressionContext $context,
?Aggregator $aggregator,
): DbalExpressionResult
{
$processedArgs = [];
$processedHavingArgs = [];
$joins = [];
$groupBy = [];
$columns = [];

[$normalized, $newAggregator] = $this->normalizeFunctions($args);
if ($newAggregator !== null) {
if ($aggregator !== null) throw new InvalidStateException("Cannot apply two aggregations simultaneously.");
$aggregator = $newAggregator;
}

$requiresHaving = false;
$expressions = [];
foreach ($normalized as $collectionFunctionArgs) {
$expression = $helper->processExpression($builder, $collectionFunctionArgs, $context, $aggregator);
$expression = $expression->applyAggregator($builder, $context);
$whereArgs = $expression->getArgsForExpansion();
if ($whereArgs !== []) {
$processedArgs[] = $whereArgs;
}
$havingArgs = $expression->getHavingArgsForExpansion();
if ($havingArgs !== []) {
$processedHavingArgs[] = $havingArgs;
$expressions[] = $expression = $helper->processExpression($builder, $collectionFunctionArgs, $aggregator);
if ($expression->havingExpression !== null) {
$requiresHaving = true;
}
$joins = array_merge($joins, $expression->joins);
$groupBy = array_merge($groupBy, $expression->groupBy);
$columns = array_merge($columns, $expression->columns);
}

if ($context === ExpressionContext::FilterOr && $processedHavingArgs !== []) {
// move all where expressions to HAVING clause
return new DbalExpressionResult(
expression: null,
args: [],
joins: $helper->mergeJoins($dbalModifier, $joins),
groupBy: array_merge($groupBy, $columns),
havingExpression: $dbalModifier,
havingArgs: [array_merge($processedArgs, $processedHavingArgs)],
columns: [],
);
} else {
return new DbalExpressionResult(
expression: $processedArgs === [] ? null : $dbalModifier,
args: $processedArgs === [] ? [] : [$processedArgs],
joins: $helper->mergeJoins($dbalModifier, $joins),
groupBy: $groupBy,
havingExpression: $processedHavingArgs === [] ? null : $dbalModifier,
havingArgs: $processedHavingArgs === [] ? [] : [$processedHavingArgs],
columns: $columns,
);
}
return new DbalExpressionResult(
expression: $dbalModifier,
args: $expressions,
havingExpression: $requiresHaving ? $dbalModifier : null,
collectFun: function (ExpressionContext $context) use ($helper, $dbalModifier) {
/** @var DbalExpressionResult $this */

$processedArgs = [];
$processedHavingArgs = [];
$joins = [];
$groupBy = [];
$columns = [];

if ($dbalModifier === '%or') {
if ($context === ExpressionContext::FilterAnd) {
$finalContext = ExpressionContext::FilterOr;
} elseif ($context === ExpressionContext::FilterAndWithHavingClause) {
$finalContext = ExpressionContext::FilterOrWithHavingClause;
} else {
$finalContext = $context;
}
} else {
$finalContext = $context;
}

foreach ($this->args as $arg) {
assert($arg instanceof DbalExpressionResult);
$expression = $arg->collect($finalContext)->applyAggregator($finalContext);

$whereArgs = $expression->getArgsForExpansion();
if ($whereArgs !== []) {
$processedArgs[] = $whereArgs;
}
$havingArgs = $expression->getHavingArgsForExpansion();
if ($havingArgs !== []) {
$processedHavingArgs[] = $havingArgs;
}
$joins = array_merge($joins, $expression->joins);
$groupBy = array_merge($groupBy, $expression->groupBy);
$columns = array_merge($columns, $expression->columns);
}

return new DbalExpressionResult(
expression: $dbalModifier,
args: [$processedArgs],
joins: $helper->mergeJoins($dbalModifier, $joins),
groupBy: $groupBy,
havingExpression: $processedHavingArgs === [] ? null : $dbalModifier,
havingArgs: $processedHavingArgs === [] ? [] : [$processedHavingArgs],
columns: $columns,
);
},
);
}
}
30 changes: 30 additions & 0 deletions src/Collection/Functions/Result/DbalExpressionResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class DbalExpressionResult
* @param PropertyMetadata|null $propertyMetadata Reference to backing property of the expression. If null, the expression is no more a simple property expression.
* @param (callable(mixed): mixed)|null $valueNormalizer Normalizes the value for better PHP comparison, it considers the backing property type.
* @param literal-string|list<literal-string|null>|null $dbalModifier Dbal modifier for particular column. Array if multi-column. Null value means expression is a general expression.
* @param (\Closure(ExpressionContext): DbalExpressionResult)|null $collectFun
* @param-closure-this DbalExpressionResult $collectFun
*/
public function __construct(
public readonly string|null $expression,
Expand All @@ -55,6 +57,7 @@ public function __construct(
public readonly ?PropertyMetadata $propertyMetadata = null,
?callable $valueNormalizer = null,
public readonly string|array|null $dbalModifier = null,
public readonly \Closure|null $collectFun = null,
)
{
$this->valueNormalizer = $valueNormalizer;
Expand Down Expand Up @@ -160,6 +163,33 @@ public function withHavingArgs(string $havingExpression, array $havingArgs): Dba
}


public function collect(ExpressionContext $context): DbalExpressionResult
{
if ($this->collectFun === null) {
if ($context === ExpressionContext::FilterOrWithHavingClause && $this->expression !== null && $this->aggregator === null) {
return new DbalExpressionResult(
expression: null,
args: [],
joins: $this->joins,
groupBy: $this->groupBy,
havingExpression: $this->expression,
havingArgs: $this->args,
columns: $this->columns,
aggregator: $this->aggregator,
propertyMetadata: $this->propertyMetadata,
valueNormalizer: $this->valueNormalizer,
dbalModifier: $this->dbalModifier,
collectFun: null,
);
}
return $this;
} else {
$collectFun = $this->collectFun->bindTo($this);
return $collectFun($context);
}
}


/**
* Applies the aggregator and returns modified expression result.
*/
Expand Down
4 changes: 1 addition & 3 deletions src/Collection/Helpers/DbalQueryBuilderHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use Nextras\Dbal\Platforms\Data\Fqn;
use Nextras\Dbal\QueryBuilder\QueryBuilder;
use Nextras\Orm\Collection\Aggregations\Aggregator;
use Nextras\Orm\Collection\Expression\ExpressionContext;
use Nextras\Orm\Collection\Functions\ConjunctionOperatorFunction;
use Nextras\Orm\Collection\Functions\FetchPropertyFunction;
use Nextras\Orm\Collection\Functions\Result\DbalExpressionResult;
Expand Down Expand Up @@ -76,7 +75,6 @@ public function __construct(
public function processExpression(
QueryBuilder $builder,
array|string $expression,
ExpressionContext $context,
?Aggregator $aggregator,
): DbalExpressionResult
{
Expand All @@ -88,7 +86,7 @@ public function processExpression(
}

$collectionFunction = $this->repository->getCollectionFunction($function);
return $collectionFunction->processDbalExpression($this, $builder, $expression, $context, $aggregator);
return $collectionFunction->processDbalExpression($this, $builder, $expression, $aggregator);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

use Nextras\Dbal\Platforms\Data\Fqn;
use Nextras\Orm\Collection\Aggregations\AnyAggregator;
use Nextras\Orm\Collection\Expression\ExpressionContext;
use Nextras\Orm\Collection\Functions\FetchPropertyFunction;
use Nextras\Orm\Collection\Helpers\DbalQueryBuilderHelper;
use Nextras\Orm\Mapper\Dbal\DbalMapper;
Expand Down Expand Up @@ -40,7 +39,6 @@ public function testManyHasOneJoin(): void
$helper,
$builder,
['author->name'],
ExpressionContext::FilterAnd,
);
Assert::count(0, $expression->groupBy);
Assert::count(1, $expression->joins);
Expand All @@ -63,7 +61,6 @@ public function testOneHasManyJoin(): void
$helper,
$builder,
['books->title'],
ExpressionContext::FilterAnd,
);
if ($this->section === Helper::SECTION_MSSQL) {
Assert::count(5, $expression->groupBy); // contains additional columns from SELECT clause
Expand All @@ -80,7 +77,6 @@ public function testOneHasManyJoin(): void
$helper,
$builder,
['books->title'],
ExpressionContext::FilterAnd,
new AnyAggregator('any2'),
);
if ($this->section === Helper::SECTION_MSSQL) {
Expand Down Expand Up @@ -109,7 +105,6 @@ public function testOneHasOneJoin(): void
$helper,
$builder,
['book->title'],
ExpressionContext::FilterAnd,
);
Assert::count(0, $expression->groupBy);
Assert::count(1, $expression->joins);
Expand Down
Loading

0 comments on commit ee8b661

Please sign in to comment.