From f53dc5b669a4f5d118f68fbe470dce2bcdc46bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20W=C3=B3js?= Date: Tue, 17 Oct 2023 16:20:47 +0200 Subject: [PATCH 1/8] IBX-6827: Added method to get all available terms and ranges from aggregation results --- .../AggregationResult/RangeAggregationResult.php | 12 ++++++++++++ .../AggregationResult/TermAggregationResult.php | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/contracts/Repository/Values/Content/Search/AggregationResult/RangeAggregationResult.php b/src/contracts/Repository/Values/Content/Search/AggregationResult/RangeAggregationResult.php index 52996d65eb..8f7d98908e 100644 --- a/src/contracts/Repository/Values/Content/Search/AggregationResult/RangeAggregationResult.php +++ b/src/contracts/Repository/Values/Content/Search/AggregationResult/RangeAggregationResult.php @@ -59,6 +59,18 @@ public function hasEntry(Range $key): bool return $this->getEntry($key) !== null; } + /** + * Return available keys (ranges). + * + * @return iterable<\Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range> + */ + public function getKeys(): iterable + { + foreach ($this->entries as $entry) { + yield $entry->getKey(); + } + } + public function count(): int { return count($this->entries); diff --git a/src/contracts/Repository/Values/Content/Search/AggregationResult/TermAggregationResult.php b/src/contracts/Repository/Values/Content/Search/AggregationResult/TermAggregationResult.php index 5eec15e660..cf2f0e4412 100644 --- a/src/contracts/Repository/Values/Content/Search/AggregationResult/TermAggregationResult.php +++ b/src/contracts/Repository/Values/Content/Search/AggregationResult/TermAggregationResult.php @@ -62,6 +62,18 @@ public function hasEntry($key): bool return $this->getEntry($key) !== null; } + /** + * Returns available keys (terms). + * + * @return iterable + */ + public function getKeys(): iterable + { + foreach ($this->entries as $entry) { + yield $entry->getKey(); + } + } + public function count(): int { return count($this->entries); From 7a4a32bf6beaedc4b837ca9729f3777e25c06e75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20W=C3=B3js?= Date: Sun, 22 Oct 2023 09:50:33 +0200 Subject: [PATCH 2/8] IBX-6827: Allow to add label aggregation ranges --- .../Content/Query/Aggregation/Range.php | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Range.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Range.php index b5ec1d20b2..1de74dca04 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Range.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Range.php @@ -27,12 +27,15 @@ final class Range extends ValueObject */ private $to; - public function __construct($from, $to) + private ?string $label; + + public function __construct($from, $to, ?string $label = null) { parent::__construct(); $this->from = $from; $this->to = $to; + $this->label = $label; } public function getFrom() @@ -45,8 +48,27 @@ public function getTo() return $this->to; } + public function getLabel(): ?string + { + return $this->label; + } + + public function setLabel(?string $label): void + { + $this->label = $label; + } + public function __toString(): string { + if ($this->label !== null) { + return sprintf( + '%s:[%s;%s)', + $this->label, + $this->getRangeValueAsString($this->from), + $this->getRangeValueAsString($this->to) + ); + } + return sprintf( '[%s;%s)', $this->getRangeValueAsString($this->from), From ce5abff511f5f41460510d7ebff653a908eacc34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20W=C3=B3js?= Date: Sun, 22 Oct 2023 12:03:56 +0200 Subject: [PATCH 3/8] IBX-6827: Introduced Range::INF const --- .../Repository/Values/Content/Query/Aggregation/Range.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Range.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Range.php index 1de74dca04..5179df322b 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Range.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Range.php @@ -13,6 +13,8 @@ final class Range extends ValueObject { + public const INF = null; + /** * Beginning of the range (included). * From 6aecb0bbec7df21352732a26e9dddb1bf1fa0cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20W=C3=B3js?= Date: Mon, 23 Oct 2023 12:06:19 +0200 Subject: [PATCH 4/8] IBX-6827: Ranges generators --- .../Field/AbstractFieldRangeAggregation.php | 16 +++ .../Content/Query/Aggregation/Range.php | 5 + .../Ranges/DateTimeStepRangesGenerator.php | 125 ++++++++++++++++++ .../Ranges/FloatStepRangesGenerator.php | 111 ++++++++++++++++ .../Ranges/IntegerStepRangesGenerator.php | 107 +++++++++++++++ .../Ranges/RangesGeneratorInterface.php | 12 ++ 6 files changed, 376 insertions(+) create mode 100644 src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/DateTimeStepRangesGenerator.php create mode 100644 src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/FloatStepRangesGenerator.php create mode 100644 src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGenerator.php create mode 100644 src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/RangesGeneratorInterface.php diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Field/AbstractFieldRangeAggregation.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Field/AbstractFieldRangeAggregation.php index e63e888c96..41ce7ece7e 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Field/AbstractFieldRangeAggregation.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Field/AbstractFieldRangeAggregation.php @@ -10,6 +10,8 @@ use Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\AbstractRangeAggregation; use Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\FieldAggregation; +use Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Ranges\RangesGeneratorInterface; +use Traversable; abstract class AbstractFieldRangeAggregation extends AbstractRangeAggregation implements FieldAggregation { @@ -26,6 +28,20 @@ public function __construct( $this->contentTypeIdentifier = $contentTypeIdentifier; $this->fieldDefinitionIdentifier = $fieldDefinitionIdentifier; } + + public static function fromGenerator( + string $name, + string $contentTypeIdentifier, + string $fieldDefinitionIdentifier, + RangesGeneratorInterface $generator + ): self { + $ranges = $generator->generate(); + if ($ranges instanceof Traversable) { + $ranges = iterator_to_array($ranges); + } + + return new static($name, $contentTypeIdentifier, $fieldDefinitionIdentifier, $ranges); + } } class_alias(AbstractFieldRangeAggregation::class, 'eZ\Publish\API\Repository\Values\Content\Query\Aggregation\Field\AbstractFieldRangeAggregation'); diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Range.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Range.php index 5179df322b..328f4ee116 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Range.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Range.php @@ -78,6 +78,11 @@ public function __toString(): string ); } + public function equalsTo(Range $value): bool + { + return $this->from === $value->from && $this->to === $value->to; + } + private function getRangeValueAsString($value): string { if ($value === null) { diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/DateTimeStepRangesGenerator.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/DateTimeStepRangesGenerator.php new file mode 100644 index 0000000000..7b3e7d0589 --- /dev/null +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/DateTimeStepRangesGenerator.php @@ -0,0 +1,125 @@ +start = $start; + $this->end = $end; + $this->step = new DateInterval('P1D'); + } + + public function getStart(): DateTimeInterface + { + return $this->start; + } + + public function setStart(DateTimeInterface $start): self + { + $this->start = $start; + + return $this; + } + + public function getEnd(): DateTimeInterface + { + return $this->end; + } + + public function setEnd(DateTimeInterface $end): self + { + $this->end = $end; + + return $this; + } + + public function getStep(): DateInterval + { + return $this->step; + } + + public function setStep(DateInterval $step): self + { + $this->step = $step; + + return $this; + } + + public function isLeftOpen(): bool + { + return $this->isLeftOpen; + } + + public function setLeftOpen(bool $isLeftOpen): void + { + $this->isLeftOpen = $isLeftOpen; + } + + public function isRightOpen(): bool + { + return $this->isRightOpen; + } + + public function setRightOpen(bool $isRightOpen): self + { + $this->isRightOpen = $isRightOpen; + + return $this; + } + + /** + * @return \Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range[] + */ + public function generate(): array + { + $ranges = []; + + if ($this->isLeftOpen) { + $ranges[] = Range::ofDateTime(Range::INF, $this->start); + } + + /** @var \DateTimeImmutable $current */ + $current = $this->start; + if ($current instanceof DateTime) { + $current = DateTimeImmutable::createFromMutable($current); + } + + while ($current <= $this->end) { + $next = $current->add($this->step); + $ranges[] = Range::ofDateTime($current, $next); + $current = $next; + } + + if ($this->isRightOpen) { + $ranges[] = Range::ofDateTime($this->end, Range::INF); + } + + return $ranges; + } +} diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/FloatStepRangesGenerator.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/FloatStepRangesGenerator.php new file mode 100644 index 0000000000..24a8e72c66 --- /dev/null +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/FloatStepRangesGenerator.php @@ -0,0 +1,111 @@ +start = $start; + $this->end = $end; + } + + public function getStart(): float + { + return $this->start; + } + + public function setStart(float $start): self + { + $this->start = $start; + + return $this; + } + + public function getEnd(): float + { + return $this->end; + } + + public function setEnd(float $end): self + { + $this->end = $end; + + return $this; + } + + public function getStep(): float + { + return $this->step; + } + + public function setStep(float $step): self + { + $this->step = $step; + + return $this; + } + + public function isLeftOpen(): bool + { + return $this->isLeftOpen; + } + + public function setLeftOpen(bool $isLeftOpen): void + { + $this->isLeftOpen = $isLeftOpen; + } + + public function isRightOpen(): bool + { + return $this->isRightOpen; + } + + public function setRightOpen(bool $isRightOpen): self + { + $this->isRightOpen = $isRightOpen; + + return $this; + } + + /** + * @return \Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range[] + */ + public function generate(): array + { + $ranges = []; + + if ($this->isLeftOpen) { + $ranges[] = Range::ofFloat(Range::INF, $this->start); + } + + $values = range($this->start, $this->end, $this->step); + for ($i = 1; $i < count($values); ++$i) { + $ranges[] = Range::ofFloat($values[$i - 1], $values[$i]); + } + + if ($this->isRightOpen) { + $ranges[] = Range::ofFloat($this->end, Range::INF); + } + + return $ranges; + } +} diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGenerator.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGenerator.php new file mode 100644 index 0000000000..8322bf033f --- /dev/null +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGenerator.php @@ -0,0 +1,107 @@ +start = $start; + $this->end = $end; + } + + public function getStart(): int + { + return $this->start; + } + + public function setStart(int $start): self + { + $this->start = $start; + + return $this; + } + + public function getEnd(): int + { + return $this->end; + } + + public function setEnd(int $end): self + { + $this->end = $end; + + return $this; + } + + public function getStep(): int + { + return $this->step; + } + + public function setStep(int $step): self + { + $this->step = $step; + + return $this; + } + + public function isLeftOpen(): bool + { + return $this->isLeftOpen; + } + + public function setLeftOpen(bool $isLeftOpen): void + { + $this->isLeftOpen = $isLeftOpen; + } + + public function isRightOpen(): bool + { + return $this->isRightOpen; + } + + public function setRightOpen(bool $isRightOpen): self + { + $this->isRightOpen = $isRightOpen; + + return $this; + } + + /** + * @return \Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range[] + */ + public function generate(): \Generator + { + if ($this->isLeftOpen) { + yield Range::ofInt(Range::INF, $this->start); + } + + $values = range($this->start, $this->end, $this->step); + for ($i = 1; $i < count($values); ++$i) { + yield Range::ofInt($values[$i - 1], $values[$i]); + } + + if ($this->isRightOpen) { + yield Range::ofInt($this->end, Range::INF); + } + } +} diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/RangesGeneratorInterface.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/RangesGeneratorInterface.php new file mode 100644 index 0000000000..4bc05f7c0c --- /dev/null +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/RangesGeneratorInterface.php @@ -0,0 +1,12 @@ + Date: Sat, 4 Nov 2023 11:29:33 +0100 Subject: [PATCH 5/8] fixup! IBX-6827: Ranges generators --- .../Query/Aggregation/Ranges/DateTimeStepRangesGenerator.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/DateTimeStepRangesGenerator.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/DateTimeStepRangesGenerator.php index 7b3e7d0589..f7e5f25656 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/DateTimeStepRangesGenerator.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/DateTimeStepRangesGenerator.php @@ -12,7 +12,6 @@ use DateTime; use DateTimeImmutable; use DateTimeInterface; -use eZ\Publish\API\Repository\Values\Content\Query\Aggregation\Field\DateRangeAggregation; use Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range; final class DateTimeStepRangesGenerator implements RangesGeneratorInterface From ca2d9b6eb74aa0638385a72ca4b595d4cafcfd71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20W=C3=B3js?= Date: Wed, 6 Dec 2023 23:33:08 +0100 Subject: [PATCH 6/8] IBX-6827: Added unit tests --- .../Ranges/DateTimeStepRangesGenerator.php | 9 +- .../Ranges/FloatStepRangesGenerator.php | 10 +- .../Ranges/IntegerStepRangesGenerator.php | 13 +- .../Ranges/RangesGeneratorInterface.php | 5 + .../DateTimeStepRangesGeneratorTest.php | 114 ++++++++++++++++++ .../Ranges/FloatStepRangesGeneratorTest.php | 91 ++++++++++++++ .../Ranges/IntegerStepRangesGeneratorTest.php | 94 +++++++++++++++ 7 files changed, 331 insertions(+), 5 deletions(-) create mode 100644 tests/integration/Core/Repository/Values/Content/Query/Aggregation/Ranges/DateTimeStepRangesGeneratorTest.php create mode 100644 tests/integration/Core/Repository/Values/Content/Query/Aggregation/Ranges/FloatStepRangesGeneratorTest.php create mode 100644 tests/integration/Core/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGeneratorTest.php diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/DateTimeStepRangesGenerator.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/DateTimeStepRangesGenerator.php index f7e5f25656..d68036c815 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/DateTimeStepRangesGenerator.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/DateTimeStepRangesGenerator.php @@ -26,7 +26,6 @@ final class DateTimeStepRangesGenerator implements RangesGeneratorInterface private bool $isRightOpen = true; - public function __construct(DateTimeInterface $start, DateTimeInterface $end) { $this->start = $start; @@ -97,6 +96,12 @@ public function setRightOpen(bool $isRightOpen): self */ public function generate(): array { + if ($this->start == $this->end && $this->isLeftOpen && $this->isRightOpen) { + return [ + new Range(Range::INF, Range::INF), + ]; + } + $ranges = []; if ($this->isLeftOpen) { @@ -109,7 +114,7 @@ public function generate(): array $current = DateTimeImmutable::createFromMutable($current); } - while ($current <= $this->end) { + while ($current < $this->end) { $next = $current->add($this->step); $ranges[] = Range::ofDateTime($current, $next); $current = $next; diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/FloatStepRangesGenerator.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/FloatStepRangesGenerator.php index 24a8e72c66..43fdec93a7 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/FloatStepRangesGenerator.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/FloatStepRangesGenerator.php @@ -69,9 +69,11 @@ public function isLeftOpen(): bool return $this->isLeftOpen; } - public function setLeftOpen(bool $isLeftOpen): void + public function setLeftOpen(bool $isLeftOpen): self { $this->isLeftOpen = $isLeftOpen; + + return $this; } public function isRightOpen(): bool @@ -91,6 +93,12 @@ public function setRightOpen(bool $isRightOpen): self */ public function generate(): array { + if ($this->start === $this->end && $this->isLeftOpen && $this->isRightOpen) { + return [ + new Range(Range::INF, Range::INF), + ]; + } + $ranges = []; if ($this->isLeftOpen) { diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGenerator.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGenerator.php index 8322bf033f..015ec765df 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGenerator.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGenerator.php @@ -8,6 +8,7 @@ namespace Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Ranges; +use Generator; use Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range; final class IntegerStepRangesGenerator implements RangesGeneratorInterface @@ -69,9 +70,11 @@ public function isLeftOpen(): bool return $this->isLeftOpen; } - public function setLeftOpen(bool $isLeftOpen): void + public function setLeftOpen(bool $isLeftOpen): self { $this->isLeftOpen = $isLeftOpen; + + return $this; } public function isRightOpen(): bool @@ -89,8 +92,14 @@ public function setRightOpen(bool $isRightOpen): self /** * @return \Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range[] */ - public function generate(): \Generator + public function generate(): Generator { + if ($this->start === $this->end && $this->isLeftOpen && $this->isRightOpen) { + yield new Range(Range::INF, Range::INF); + + return; + } + if ($this->isLeftOpen) { yield Range::ofInt(Range::INF, $this->start); } diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/RangesGeneratorInterface.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/RangesGeneratorInterface.php index 4bc05f7c0c..e059013ca6 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/RangesGeneratorInterface.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/RangesGeneratorInterface.php @@ -1,4 +1,9 @@ createRange(Range::INF, '01-01-2023'), + $this->createRange('01-01-2023', '02-01-2023'), + $this->createRange('02-01-2023', '03-01-2023'), + $this->createRange('03-01-2023', Range::INF), + ], + new DateTimeStepRangesGenerator( + new DateTimeImmutable('01-01-2023 00:00:00'), + new DateTimeImmutable('03-01-2023 00:00:00') + ) + ); + } + + public function testGenerateCloseRangesSequence(): void + { + $generator = new DateTimeStepRangesGenerator( + new DateTimeImmutable('01-01-2023 00:00:00'), + new DateTimeImmutable('03-01-2023 00:00:00') + ); + $generator->setLeftOpen(false); + $generator->setRightOpen(false); + + self::assertGeneratorResults( + [ + $this->createRange('01-01-2023', '02-01-2023'), + $this->createRange('02-01-2023', '03-01-2023'), + ], + $generator + ); + } + + public function testGenerateRangesWithCustomStep(): void + { + $generator = new DateTimeStepRangesGenerator( + new DateTimeImmutable('01-01-2023 00:00:00'), + new DateTimeImmutable('05-01-2023 00:00:00') + ); + $generator->setStep(new DateInterval('P2D')); + + self::assertGeneratorResults( + [ + $this->createRange(Range::INF, '01-01-2023'), + $this->createRange('01-01-2023', '03-01-2023'), + $this->createRange('03-01-2023', '05-01-2023'), + $this->createRange('05-01-2023', Range::INF), + ], + $generator + ); + } + + public function testGenerateInfRangesSequence(): void + { + $generator = new DateTimeStepRangesGenerator( + new DateTimeImmutable('01-01-1970 00:00:00'), + new DateTimeImmutable('01-01-1970 00:00:00'), + ); + + self::assertGeneratorResults( + [ + Range::ofDateTime(Range::INF, Range::INF), + ], + $generator + ); + } + + public function testGenerateEmptyRangesSequence(): void + { + $generator = new DateTimeStepRangesGenerator( + new DateTimeImmutable('01-01-1970 00:00:00'), + new DateTimeImmutable('01-01-1970 00:00:00'), + ); + $generator->setLeftOpen(false); + $generator->setRightOpen(false); + + self::assertGeneratorResults([], $generator); + } + + private function createRange(?string $start, ?string $end): Range + { + return Range::ofDateTime( + $start !== null ? new DateTimeImmutable($start . ' 00:00:00') : null, + $end !== null ? new DateTimeImmutable($end . ' 00:00:00') : null + ); + } + + /** + * @param \Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range[] $expectedResult + */ + private static function assertGeneratorResults(array $expectedResult, DateTimeStepRangesGenerator $generator): void + { + self::assertEquals($expectedResult, $generator->generate()); + } +} diff --git a/tests/integration/Core/Repository/Values/Content/Query/Aggregation/Ranges/FloatStepRangesGeneratorTest.php b/tests/integration/Core/Repository/Values/Content/Query/Aggregation/Ranges/FloatStepRangesGeneratorTest.php new file mode 100644 index 0000000000..f41391acab --- /dev/null +++ b/tests/integration/Core/Repository/Values/Content/Query/Aggregation/Ranges/FloatStepRangesGeneratorTest.php @@ -0,0 +1,91 @@ +setLeftOpen(false); + $generator->setRightOpen(false); + + self::assertGeneratorResults( + [ + Range::ofFloat(1.0, 2.0), + Range::ofFloat(2.0, 3.0), + ], + $generator + ); + } + + public function testGenerateRangesWithCustomStep(): void + { + $generator = new FloatStepRangesGenerator(1.0, 10.0); + $generator->setStep(2.0); + + self::assertGeneratorResults( + [ + Range::ofFloat(Range::INF, 1.0), + Range::ofFloat(1.0, 3.0), + Range::ofFloat(3.0, 5.0), + Range::ofFloat(5.0, 7.0), + Range::ofFloat(7.0, 9.0), + Range::ofFloat(10.0, Range::INF), + ], + $generator + ); + } + + public function testGenerateInfRangesSequence(): void + { + $generator = new FloatStepRangesGenerator(0.0, 0.0); + + self::assertGeneratorResults( + [ + Range::ofFloat(Range::INF, Range::INF), + ], + $generator + ); + } + + public function testGenerateEmptyRangesSequence(): void + { + $generator = new FloatStepRangesGenerator(0.0, 0.0); + $generator->setLeftOpen(false); + $generator->setRightOpen(false); + + self::assertGeneratorResults([], $generator); + } + + /** + * @param \Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range[] $expectedResult + */ + private static function assertGeneratorResults(array $expectedResult, FloatStepRangesGenerator $generator): void + { + self::assertEquals($expectedResult, $generator->generate()); + } +} diff --git a/tests/integration/Core/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGeneratorTest.php b/tests/integration/Core/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGeneratorTest.php new file mode 100644 index 0000000000..272e2e095b --- /dev/null +++ b/tests/integration/Core/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGeneratorTest.php @@ -0,0 +1,94 @@ +setLeftOpen(false); + $generator->setRightOpen(false); + + self::assertGeneratorResults( + [ + Range::ofInt(1, 2), + Range::ofInt(2, 3), + ], + $generator + ); + } + + public function testGenerateRangesWithCustomStep(): void + { + $generator = new IntegerStepRangesGenerator(1, 10); + $generator->setStep(2); + + self::assertGeneratorResults( + [ + Range::ofInt(Range::INF, 1), + Range::ofInt(1, 3), + Range::ofInt(3, 5), + Range::ofInt(5, 7), + Range::ofInt(7, 9), + Range::ofInt(10, Range::INF), + ], + $generator + ); + } + + public function testGenerateInfRangesSequence(): void + { + $generator = new IntegerStepRangesGenerator(0, 0); + + self::assertGeneratorResults( + [ + Range::ofInt(Range::INF, Range::INF), + ], + $generator + ); + } + + public function testGenerateEmptyRangesSequence(): void + { + $generator = new IntegerStepRangesGenerator(0, 0); + $generator->setLeftOpen(false); + $generator->setRightOpen(false); + + self::assertGeneratorResults([], $generator); + } + + /** + * @param \Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range[] $expectedResult + */ + private static function assertGeneratorResults(array $expectedResult, IntegerStepRangesGenerator $generator): void + { + self::assertEquals( + $expectedResult, + iterator_to_array($generator->generate()) + ); + } +} From 92f2d7530e5ed9d24041a228b3274a710c7f7b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20W=C3=B3js?= Date: Wed, 6 Dec 2023 23:59:41 +0100 Subject: [PATCH 7/8] fixup! IBX-6827: Ranges generators --- .../Aggregation/DateMetadataRangeAggregation.php | 16 ++++++++++++++++ .../Field/AbstractFieldRangeAggregation.php | 16 ---------------- .../Aggregation/Field/DateRangeAggregation.php | 16 ++++++++++++++++ .../Field/DateTimeRangeAggregation.php | 16 ++++++++++++++++ .../Aggregation/Field/FloatRangeAggregation.php | 16 ++++++++++++++++ .../Field/IntegerRangeAggregation.php | 16 ++++++++++++++++ .../Aggregation/Field/TimeRangeAggregation.php | 16 ++++++++++++++++ .../Ranges/IntegerStepRangesGenerator.php | 2 +- .../Query/Aggregation/RawRangeAggregation.php | 16 ++++++++++++++++ 9 files changed, 113 insertions(+), 17 deletions(-) diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/DateMetadataRangeAggregation.php b/src/contracts/Repository/Values/Content/Query/Aggregation/DateMetadataRangeAggregation.php index 33d54bf039..2dfa08ad53 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/DateMetadataRangeAggregation.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/DateMetadataRangeAggregation.php @@ -8,6 +8,9 @@ namespace Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation; +use Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Ranges\RangesGeneratorInterface; +use Traversable; + final class DateMetadataRangeAggregation extends AbstractRangeAggregation { public const MODIFIED = 'modified'; @@ -27,6 +30,19 @@ public function getType(): string { return $this->type; } + + public static function fromGenerator( + string $name, + string $type, + RangesGeneratorInterface $generator + ): self { + $ranges = $generator->generate(); + if ($ranges instanceof Traversable) { + $ranges = iterator_to_array($ranges); + } + + return new self($name, $type, $ranges); + } } class_alias(DateMetadataRangeAggregation::class, 'eZ\Publish\API\Repository\Values\Content\Query\Aggregation\DateMetadataRangeAggregation'); diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Field/AbstractFieldRangeAggregation.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Field/AbstractFieldRangeAggregation.php index 41ce7ece7e..e63e888c96 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Field/AbstractFieldRangeAggregation.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Field/AbstractFieldRangeAggregation.php @@ -10,8 +10,6 @@ use Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\AbstractRangeAggregation; use Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\FieldAggregation; -use Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Ranges\RangesGeneratorInterface; -use Traversable; abstract class AbstractFieldRangeAggregation extends AbstractRangeAggregation implements FieldAggregation { @@ -28,20 +26,6 @@ public function __construct( $this->contentTypeIdentifier = $contentTypeIdentifier; $this->fieldDefinitionIdentifier = $fieldDefinitionIdentifier; } - - public static function fromGenerator( - string $name, - string $contentTypeIdentifier, - string $fieldDefinitionIdentifier, - RangesGeneratorInterface $generator - ): self { - $ranges = $generator->generate(); - if ($ranges instanceof Traversable) { - $ranges = iterator_to_array($ranges); - } - - return new static($name, $contentTypeIdentifier, $fieldDefinitionIdentifier, $ranges); - } } class_alias(AbstractFieldRangeAggregation::class, 'eZ\Publish\API\Repository\Values\Content\Query\Aggregation\Field\AbstractFieldRangeAggregation'); diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Field/DateRangeAggregation.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Field/DateRangeAggregation.php index 0be15c9413..8b225caf25 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Field/DateRangeAggregation.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Field/DateRangeAggregation.php @@ -8,8 +8,24 @@ namespace Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Field; +use Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Ranges\RangesGeneratorInterface; +use Traversable; + final class DateRangeAggregation extends AbstractFieldRangeAggregation { + public static function fromGenerator( + string $name, + string $contentTypeIdentifier, + string $fieldDefinitionIdentifier, + RangesGeneratorInterface $generator + ): self { + $ranges = $generator->generate(); + if ($ranges instanceof Traversable) { + $ranges = iterator_to_array($ranges); + } + + return new self($name, $contentTypeIdentifier, $fieldDefinitionIdentifier, $ranges); + } } class_alias(DateRangeAggregation::class, 'eZ\Publish\API\Repository\Values\Content\Query\Aggregation\Field\DateRangeAggregation'); diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Field/DateTimeRangeAggregation.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Field/DateTimeRangeAggregation.php index a6a155b62b..3963444ecc 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Field/DateTimeRangeAggregation.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Field/DateTimeRangeAggregation.php @@ -8,8 +8,24 @@ namespace Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Field; +use Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Ranges\RangesGeneratorInterface; +use Traversable; + final class DateTimeRangeAggregation extends AbstractFieldRangeAggregation { + public static function fromGenerator( + string $name, + string $contentTypeIdentifier, + string $fieldDefinitionIdentifier, + RangesGeneratorInterface $generator + ): self { + $ranges = $generator->generate(); + if ($ranges instanceof Traversable) { + $ranges = iterator_to_array($ranges); + } + + return new self($name, $contentTypeIdentifier, $fieldDefinitionIdentifier, $ranges); + } } class_alias(DateTimeRangeAggregation::class, 'eZ\Publish\API\Repository\Values\Content\Query\Aggregation\Field\DateTimeRangeAggregation'); diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Field/FloatRangeAggregation.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Field/FloatRangeAggregation.php index 9c2ad230c2..188a15396f 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Field/FloatRangeAggregation.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Field/FloatRangeAggregation.php @@ -8,8 +8,24 @@ namespace Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Field; +use Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Ranges\RangesGeneratorInterface; +use Traversable; + final class FloatRangeAggregation extends AbstractFieldRangeAggregation { + public static function fromGenerator( + string $name, + string $contentTypeIdentifier, + string $fieldDefinitionIdentifier, + RangesGeneratorInterface $generator + ): self { + $ranges = $generator->generate(); + if ($ranges instanceof Traversable) { + $ranges = iterator_to_array($ranges); + } + + return new self($name, $contentTypeIdentifier, $fieldDefinitionIdentifier, $ranges); + } } class_alias(FloatRangeAggregation::class, 'eZ\Publish\API\Repository\Values\Content\Query\Aggregation\Field\FloatRangeAggregation'); diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Field/IntegerRangeAggregation.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Field/IntegerRangeAggregation.php index 3fdde15481..e79fe6dd28 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Field/IntegerRangeAggregation.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Field/IntegerRangeAggregation.php @@ -8,8 +8,24 @@ namespace Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Field; +use Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Ranges\RangesGeneratorInterface; +use Traversable; + final class IntegerRangeAggregation extends AbstractFieldRangeAggregation { + public static function fromGenerator( + string $name, + string $contentTypeIdentifier, + string $fieldDefinitionIdentifier, + RangesGeneratorInterface $generator + ): self { + $ranges = $generator->generate(); + if ($ranges instanceof Traversable) { + $ranges = iterator_to_array($ranges); + } + + return new static($name, $contentTypeIdentifier, $fieldDefinitionIdentifier, $ranges); + } } class_alias(IntegerRangeAggregation::class, 'eZ\Publish\API\Repository\Values\Content\Query\Aggregation\Field\IntegerRangeAggregation'); diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Field/TimeRangeAggregation.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Field/TimeRangeAggregation.php index 7b2d5c9c7c..30907ec4b1 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Field/TimeRangeAggregation.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Field/TimeRangeAggregation.php @@ -8,8 +8,24 @@ namespace Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Field; +use Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Ranges\RangesGeneratorInterface; +use Traversable; + final class TimeRangeAggregation extends AbstractFieldRangeAggregation { + public static function fromGenerator( + string $name, + string $contentTypeIdentifier, + string $fieldDefinitionIdentifier, + RangesGeneratorInterface $generator + ): self { + $ranges = $generator->generate(); + if ($ranges instanceof Traversable) { + $ranges = iterator_to_array($ranges); + } + + return new self($name, $contentTypeIdentifier, $fieldDefinitionIdentifier, $ranges); + } } class_alias(TimeRangeAggregation::class, 'eZ\Publish\API\Repository\Values\Content\Query\Aggregation\Field\TimeRangeAggregation'); diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGenerator.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGenerator.php index 015ec765df..b728057d90 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGenerator.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGenerator.php @@ -90,7 +90,7 @@ public function setRightOpen(bool $isRightOpen): self } /** - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range[] + * @return \Generator<\Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Range> */ public function generate(): Generator { diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/RawRangeAggregation.php b/src/contracts/Repository/Values/Content/Query/Aggregation/RawRangeAggregation.php index a5ebeabd43..2aac8a6bb8 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/RawRangeAggregation.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/RawRangeAggregation.php @@ -8,6 +8,9 @@ namespace Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation; +use Ibexa\Contracts\Core\Repository\Values\Content\Query\Aggregation\Ranges\RangesGeneratorInterface; +use Traversable; + final class RawRangeAggregation extends AbstractRangeAggregation implements RawAggregation { /** @var string */ @@ -24,6 +27,19 @@ public function getFieldName(): string { return $this->fieldName; } + + public static function fromGenerator( + string $name, + string $fieldName, + RangesGeneratorInterface $generator + ): self { + $ranges = $generator->generate(); + if ($ranges instanceof Traversable) { + $ranges = iterator_to_array($ranges); + } + + return new self($name, $fieldName, $ranges); + } } class_alias(RawRangeAggregation::class, 'eZ\Publish\API\Repository\Values\Content\Query\Aggregation\RawRangeAggregation'); From 2f108446cb4c9ddbe2d31dd45c4a5c9fe8ce39f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20W=C3=B3js?= Date: Mon, 11 Dec 2023 15:53:47 +0100 Subject: [PATCH 8/8] Applied code review suggestions --- .../Content/Query/Aggregation/Range.php | 2 +- .../Ranges/FloatStepRangesGenerator.php | 2 +- .../Ranges/IntegerStepRangesGenerator.php | 2 +- .../Content/Query/Aggregation/RangeTest.php | 63 +++++++++++++++++++ 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Range.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Range.php index 328f4ee116..aea16b7214 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Range.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Range.php @@ -80,7 +80,7 @@ public function __toString(): string public function equalsTo(Range $value): bool { - return $this->from === $value->from && $this->to === $value->to; + return $this->from == $value->from && $this->to == $value->to; } private function getRangeValueAsString($value): string diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/FloatStepRangesGenerator.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/FloatStepRangesGenerator.php index 43fdec93a7..fd265d190d 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/FloatStepRangesGenerator.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/FloatStepRangesGenerator.php @@ -106,7 +106,7 @@ public function generate(): array } $values = range($this->start, $this->end, $this->step); - for ($i = 1; $i < count($values); ++$i) { + for ($i = 1, $count = count($values); $i < $count; ++$i) { $ranges[] = Range::ofFloat($values[$i - 1], $values[$i]); } diff --git a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGenerator.php b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGenerator.php index b728057d90..154f172e53 100644 --- a/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGenerator.php +++ b/src/contracts/Repository/Values/Content/Query/Aggregation/Ranges/IntegerStepRangesGenerator.php @@ -105,7 +105,7 @@ public function generate(): Generator } $values = range($this->start, $this->end, $this->step); - for ($i = 1; $i < count($values); ++$i) { + for ($i = 1, $count = count($values); $i < $count; ++$i) { yield Range::ofInt($values[$i - 1], $values[$i]); } diff --git a/tests/lib/Repository/Values/Content/Query/Aggregation/RangeTest.php b/tests/lib/Repository/Values/Content/Query/Aggregation/RangeTest.php index afd4a73421..7cffc94f41 100644 --- a/tests/lib/Repository/Values/Content/Query/Aggregation/RangeTest.php +++ b/tests/lib/Repository/Values/Content/Query/Aggregation/RangeTest.php @@ -71,6 +71,69 @@ public function testOfDateTime(): void $this->assertEquals(new Range($a, $b), Range::ofDateTime($a, $b)); $this->assertEquals(new Range($a, null), Range::ofDateTime($a, null)); } + + /** + * @dataProvider dataProviderForEqualsTo + */ + public function testEqualsTo(Range $rangeA, Range $rangeB, bool $expectedResult): void + { + self::assertEquals($expectedResult, $rangeA->equalsTo($rangeB)); + self::assertEquals($expectedResult, $rangeB->equalsTo($rangeA)); + } + + /** + * @return iterable + */ + public function dataProviderForEqualsTo(): iterable + { + yield 'int (true)' => [ + Range::ofInt(1, 10), + Range::ofInt(1, 10), + true, + ]; + + yield 'int (false)' => [ + Range::ofInt(1, 10), + Range::ofInt(1, 100), + false, + ]; + + yield 'float (true)' => [ + Range::ofFloat(1.0, 10.0), + Range::ofFloat(1.0, 10.0), + true, + ]; + + yield 'float (false)' => [ + Range::ofFloat(1.0, 10.0), + Range::ofFloat(1.0, 100.0), + false, + ]; + + yield 'data & time (true)' => [ + Range::ofDateTime( + new DateTimeImmutable('2023-01-01 00:00:00'), + new DateTimeImmutable('2023-12-01 00:00:00') + ), + Range::ofDateTime( + new DateTimeImmutable('2023-01-01 00:00:00'), + new DateTimeImmutable('2023-12-01 00:00:00') + ), + true, + ]; + + yield 'data & time (false)' => [ + Range::ofDateTime( + new DateTimeImmutable('2023-01-01 00:00:00'), + new DateTimeImmutable('2023-12-01 00:00:00') + ), + Range::ofDateTime( + new DateTimeImmutable('2024-01-01 00:00:00'), + new DateTimeImmutable('2024-12-01 00:00:00') + ), + false, + ]; + } } class_alias(RangeTest::class, 'eZ\Publish\API\Repository\Tests\Values\Content\Query\Aggregation\RangeTest');