Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ArrayCollection processes conditions as row-dependent in multi-value joins #546

Merged
merged 7 commits into from
Dec 11, 2021
Original file line number Diff line number Diff line change
@@ -1,18 +1,49 @@
<?php declare(strict_types = 1);

namespace Nextras\Orm\Collection\Helpers;
namespace Nextras\Orm\Collection\Aggregations;


use Nextras\Dbal\QueryBuilder\QueryBuilder;
use Nextras\Orm\Collection\Helpers\DbalExpressionResult;
use Nextras\Orm\Collection\Helpers\DbalJoinEntry;
use Nextras\Orm\Exception\InvalidStateException;
use function array_merge;
use function array_pop;
use function array_shift;


class DbalAnyAggregator implements IDbalAggregator
/**
* @implements IArrayAggregator<bool>
*/
class AnyAggregator implements IDbalAggregator, IArrayAggregator
{
public function aggregate(
/** @var string */
private $aggregateKey;


public function __construct(string $aggregateKey = 'any')
{
$this->aggregateKey = $aggregateKey;
}


public function getAggregateKey(): string
{
return $this->aggregateKey;
}


public function aggregateValues(array $values): bool
{
foreach ($values as $value) {
if ($value) {
return true;
}
}
return false;
}


public function aggregateExpression(
QueryBuilder $queryBuilder,
DbalExpressionResult $expression
): DbalExpressionResult
Expand All @@ -36,12 +67,12 @@ public function aggregate(
);

$primaryKey = $join->conventions->getStoragePrimaryKey()[0];
$queryBuilder->addGroupBy('%table.%column', $join->toAlias, $primaryKey);

return new DbalExpressionResult(
'COUNT(%table.%column) > 0',
[$join->toAlias, $primaryKey],
$joins,
$expression->groupBy,
null,
true,
null,
Expand Down
89 changes: 89 additions & 0 deletions src/Collection/Aggregations/CountAggregator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php declare(strict_types = 1);

namespace Nextras\Orm\Collection\Aggregations;


use Nextras\Dbal\QueryBuilder\QueryBuilder;
use Nextras\Orm\Collection\Helpers\DbalExpressionResult;
use Nextras\Orm\Collection\Helpers\DbalJoinEntry;
use Nextras\Orm\Exception\InvalidStateException;


/**
* @implements IArrayAggregator<bool>
*/
class CountAggregator implements IDbalAggregator, IArrayAggregator
{
/** @var int */
private $atLeast;

/** @var int */
private $atMost;

/** @var string */
private $aggregateKey;


public function __construct(
int $atLeast,
int $atMost,
string $aggregateKey = 'count'
)
{
$this->atLeast = $atLeast;
$this->atMost = $atMost;
$this->aggregateKey = $aggregateKey;
}


public function getAggregateKey(): string
{
return $this->aggregateKey;
}


public function aggregateValues(array $values): bool
{
$count = count(array_filter($values));
return $count >= $this->atLeast && $count <= $this->atMost;
}


public function aggregateExpression(
QueryBuilder $queryBuilder,
DbalExpressionResult $expression
): DbalExpressionResult
{
$joinExpression = $expression->expression;

$joinArgs = $expression->args;
$joins = $expression->joins;
$join = array_pop($joins);
if ($join === null) {
throw new InvalidStateException('Aggregation applied over expression without a relationship');
}

$joins[] = new DbalJoinEntry(
$join->toExpression,
$join->toArgs,
$join->toAlias,
"($join->onExpression) AND $joinExpression",
array_merge($join->onArgs, $joinArgs),
$join->conventions
);

$primaryKey = $join->conventions->getStoragePrimaryKey()[0];
$groupBy = $expression->groupBy;

return new DbalExpressionResult(
'COUNT(%table.%column) >= %i AND COUNT(%table.%column) <= %i',
[$join->toAlias, $primaryKey, $this->atLeast, $join->toAlias, $primaryKey, $this->atMost],
$joins,
$groupBy,
null,
true,
null,
null
);
}
}
9 changes: 9 additions & 0 deletions src/Collection/Aggregations/IAggregator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types = 1);

namespace Nextras\Orm\Collection\Aggregations;


interface IAggregator
{
public function getAggregateKey(): string;
}
16 changes: 16 additions & 0 deletions src/Collection/Aggregations/IArrayAggregator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php declare(strict_types = 1);

namespace Nextras\Orm\Collection\Aggregations;


/**
* @template T
*/
interface IArrayAggregator extends IAggregator
{
/**
* @param array<T> $values
* @return T
*/
function aggregateValues(array $values);
}
16 changes: 16 additions & 0 deletions src/Collection/Aggregations/IDbalAggregator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php declare(strict_types = 1);

namespace Nextras\Orm\Collection\Aggregations;


use Nextras\Dbal\QueryBuilder\QueryBuilder;
use Nextras\Orm\Collection\Helpers\DbalExpressionResult;


interface IDbalAggregator extends IAggregator
{
public function aggregateExpression(
QueryBuilder $queryBuilder,
DbalExpressionResult $expressionResult
): DbalExpressionResult;
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,49 @@
<?php declare(strict_types = 1);

namespace Nextras\Orm\Collection\Helpers;
namespace Nextras\Orm\Collection\Aggregations;


use Nextras\Dbal\QueryBuilder\QueryBuilder;
use Nextras\Orm\Collection\Helpers\DbalExpressionResult;
use Nextras\Orm\Collection\Helpers\DbalJoinEntry;
use Nextras\Orm\Exception\InvalidStateException;
use function array_merge;
use function array_pop;
use function array_shift;


class DbalNoneAggregator implements IDbalAggregator
/**
* @implements IArrayAggregator<bool>
*/
class NoneAggregator implements IDbalAggregator, IArrayAggregator
{
public function aggregate(
/** @var string */
private $aggregateKey;


public function __construct(string $aggregateKey = 'none')
{
$this->aggregateKey = $aggregateKey;
}


public function getAggregateKey(): string
{
return $this->aggregateKey;
}


public function aggregateValues(array $values): bool
{
foreach ($values as $value) {
if ($value) {
return false;
}
}
return true;
}


public function aggregateExpression(
QueryBuilder $queryBuilder,
DbalExpressionResult $expression
): DbalExpressionResult
Expand All @@ -36,12 +67,12 @@ public function aggregate(
);

$primaryKey = $join->conventions->getStoragePrimaryKey()[0];
$queryBuilder->addGroupBy('%table.%column', $join->toAlias, $primaryKey);

return new DbalExpressionResult(
'COUNT(%table.%column) = 0',
[$join->toAlias, $primaryKey],
$joins,
$expression->groupBy,
null,
true,
null,
Expand Down
7 changes: 5 additions & 2 deletions src/Collection/ArrayCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Iterator;
use Nette\Utils\Arrays;
use Nextras\Orm\Collection\Helpers\ArrayCollectionHelper;
use Nextras\Orm\Collection\Helpers\ArrayPropertyValueReference;
use Nextras\Orm\Collection\Helpers\FetchPairsHelper;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Exception\InvalidArgumentException;
Expand Down Expand Up @@ -60,7 +61,7 @@ class ArrayCollection implements ICollection, MemoryCollection

/**
* @var Closure[]
* @phpstan-var list<Closure(E): mixed>
* @phpstan-var array<Closure(E): ArrayPropertyValueReference>
*/
protected $collectionFilter = [];

Expand Down Expand Up @@ -294,7 +295,9 @@ protected function processData(): void
$data = $this->data;

foreach ($this->collectionFilter as $filter) {
$data = array_filter($data, $filter);
$data = array_filter($data, function ($value) use ($filter) {
return $filter($value)->value;
});
}

if (count($this->collectionSorter) > 0) {
Expand Down
11 changes: 11 additions & 0 deletions src/Collection/DbalCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Nextras\Orm\Mapper\IRelationshipMapper;
use function count;
use function is_array;
use function str_repeat;


/**
Expand Down Expand Up @@ -313,6 +314,7 @@ public function __clone()
public function getQueryBuilder(): QueryBuilder
{
$joins = [];
$groupBy = [];
$helper = $this->getHelper();
$args = $this->filtering;

Expand All @@ -324,6 +326,7 @@ public function getQueryBuilder(): QueryBuilder
null
);
$joins = $expression->joins;
$groupBy = $expression->groupBy;
if ($expression->isHavingClause) {
$this->queryBuilder->andHaving($expression->expression, ...$expression->args);
} else {
Expand All @@ -334,6 +337,7 @@ public function getQueryBuilder(): QueryBuilder

foreach ($this->ordering as [$expression, $direction]) {
$joins = array_merge($joins, $expression->joins);
$groupBy = array_merge($groupBy, $expression->groupBy);
$orderingExpression = $helper->processOrderDirection($expression, $direction);
$this->queryBuilder->addOrderBy('%ex', $orderingExpression);
}
Expand All @@ -344,6 +348,13 @@ public function getQueryBuilder(): QueryBuilder
$join->applyJoin($this->queryBuilder);
}

if (count($groupBy) > 0) {
$this->queryBuilder->groupBy(
'%ex' . str_repeat(', %ex', count($groupBy) - 1),
...$groupBy
);
}

return $this->queryBuilder;
}

Expand Down
48 changes: 0 additions & 48 deletions src/Collection/Functions/AnyAggregateFunction.php

This file was deleted.

Loading