From 79cba15dbac896544572259035dc56d961fe4fcc Mon Sep 17 00:00:00 2001 From: Jan Skrasek Date: Thu, 14 Mar 2024 14:55:12 +0100 Subject: [PATCH] make atLeast/atMost optional boundaries for CountAggregator --- .../Aggregations/CountAggregator.php | 79 ++++++++++++------- .../collection.aggregation.join.phpt | 24 +++++- ...ggregationJoinTest_testCountAggregator.sql | 4 + 3 files changed, 75 insertions(+), 32 deletions(-) create mode 100644 tests/sqls/NextrasTests/Orm/Integration/Collection/CollectionAggregationJoinTest_testCountAggregator.sql diff --git a/src/Collection/Aggregations/CountAggregator.php b/src/Collection/Aggregations/CountAggregator.php index 2190b4fe..5495d31b 100644 --- a/src/Collection/Aggregations/CountAggregator.php +++ b/src/Collection/Aggregations/CountAggregator.php @@ -9,6 +9,7 @@ use Nextras\Orm\Collection\Functions\Result\DbalTableJoin; use Nextras\Orm\Exception\InvalidArgumentException; use function array_filter; +use function array_merge; use function array_pop; use function count; @@ -18,26 +19,18 @@ */ class CountAggregator implements Aggregator { - private int $atLeast; - - private int $atMost; - - /** @var literal-string */ - private string $aggregateKey; - - /** * @param literal-string $aggregateKey */ public function __construct( - int $atLeast, - int $atMost, - string $aggregateKey = 'count', + private readonly ?int $atLeast, + private readonly ?int $atMost, + private readonly string $aggregateKey = 'count', ) { - $this->atLeast = $atLeast; - $this->atMost = $atMost; - $this->aggregateKey = $aggregateKey; + if ($this->atLeast === null && $this->atMost === null) { + throw new InvalidArgumentException("At least one of the limitations (\$atLeast or \$atMost) is required."); + } } @@ -50,7 +43,9 @@ public function getAggregateKey(): string public function aggregateValues(array $values): bool { $count = count(array_filter($values)); - return $count >= $this->atLeast && $count <= $this->atMost; + if ($this->atLeast !== null && $count >= $this->atLeast) return true; + if ($this->atMost !== null && $count <= $this->atMost) return true; + return false; } @@ -85,19 +80,45 @@ public function aggregateExpression( groupByColumns: $join->groupByColumns, ); - return new DbalExpressionResult( - expression: 'COUNT(%table.%column) >= %i AND COUNT(%table.%column) <= %i', - args: [ - $join->toAlias, - $join->groupByColumns[0], - $this->atLeast, - $join->toAlias, - $join->groupByColumns[0], - $this->atMost, - ], - joins: $joins, - groupBy: $expression->groupBy, - isHavingClause: true, - ); + if ($this->atLeast !== null && $this->atMost !== null) { + return new DbalExpressionResult( + expression: 'COUNT(%table.%column) >= %i AND COUNT(%table.%column) <= %i', + args: [ + $join->toAlias, + $join->groupByColumns[0], + $this->atLeast, + $join->toAlias, + $join->groupByColumns[0], + $this->atMost, + ], + joins: $joins, + groupBy: $expression->groupBy, + isHavingClause: true, + ); + } elseif ($this->atMost !== null) { + return new DbalExpressionResult( + expression: 'COUNT(%table.%column) <= %i', + args: [ + $join->toAlias, + $join->groupByColumns[0], + $this->atMost, + ], + joins: $joins, + groupBy: $expression->groupBy, + isHavingClause: true, + ); + } else { + return new DbalExpressionResult( + expression: 'COUNT(%table.%column) >= %i', + args: [ + $join->toAlias, + $join->groupByColumns[0], + $this->atLeast, + ], + joins: $joins, + groupBy: $expression->groupBy, + isHavingClause: true, + ); + } } } diff --git a/tests/cases/integration/Collection/collection.aggregation.join.phpt b/tests/cases/integration/Collection/collection.aggregation.join.phpt index d383f2ab..8027793b 100644 --- a/tests/cases/integration/Collection/collection.aggregation.join.phpt +++ b/tests/cases/integration/Collection/collection.aggregation.join.phpt @@ -61,7 +61,6 @@ class CollectionAggregationJoinTest extends DataTestCase 'books->title' => 'Book 1', 'books->translator->id' => null, ]); - $authors->fetchAll(); Assert::same(0, $authors->count()); Assert::same(0, $authors->countStored()); @@ -74,16 +73,35 @@ class CollectionAggregationJoinTest extends DataTestCase */ $authors = $this->orm->authors->findBy([ ICollection::OR, - new CountAggregator(1, 1), + new CountAggregator(atLeast: 1, atMost: 1), 'books->translator->id!=' => null, 'books->price->cents<' => 100, ]); - $authors->fetchAll(); Assert::same(1, $authors->count()); Assert::same(1, $authors->countStored()); } + public function testCountAggregator(): void + { + $authors = $this->orm->authors->findBy([ + ICollection::AND, + new CountAggregator(atLeast: 2, atMost: null), + 'books->price->cents>=' => 50, + ]); + Assert::same(1, $authors->count()); + Assert::same(1, $authors->countStored()); + + $authors = $this->orm->authors->findBy([ + ICollection::AND, + new CountAggregator(atLeast: null, atMost: 1), + 'books->price->cents>=' => 51, + ]); + Assert::same(2, $authors->count()); + Assert::same(2, $authors->countStored()); + } + + public function testHasValueOrEmptyWithFunctions(): void { /* diff --git a/tests/sqls/NextrasTests/Orm/Integration/Collection/CollectionAggregationJoinTest_testCountAggregator.sql b/tests/sqls/NextrasTests/Orm/Integration/Collection/CollectionAggregationJoinTest_testCountAggregator.sql new file mode 100644 index 00000000..a651421c --- /dev/null +++ b/tests/sqls/NextrasTests/Orm/Integration/Collection/CollectionAggregationJoinTest_testCountAggregator.sql @@ -0,0 +1,4 @@ +SELECT "authors".* FROM "public"."authors" AS "authors" LEFT JOIN "books" AS "books_count" ON (("authors"."id" = "books_count"."author_id") AND "books_count"."price" >= 50) GROUP BY "authors"."id" HAVING ((COUNT("books_count"."id") >= 2)); +SELECT COUNT(*) AS count FROM (SELECT "authors"."id" FROM "public"."authors" AS "authors" LEFT JOIN "books" AS "books_count" ON (("authors"."id" = "books_count"."author_id") AND "books_count"."price" >= 50) GROUP BY "authors"."id" HAVING ((COUNT("books_count"."id") >= 2))) temp; +SELECT "authors".* FROM "public"."authors" AS "authors" LEFT JOIN "books" AS "books_count" ON (("authors"."id" = "books_count"."author_id") AND "books_count"."price" >= 51) GROUP BY "authors"."id" HAVING ((COUNT("books_count"."id") <= 1)); +SELECT COUNT(*) AS count FROM (SELECT "authors"."id" FROM "public"."authors" AS "authors" LEFT JOIN "books" AS "books_count" ON (("authors"."id" = "books_count"."author_id") AND "books_count"."price" >= 51) GROUP BY "authors"."id" HAVING ((COUNT("books_count"."id") <= 1))) temp;