From 2fd795365c34d2012a48152574972352169ae196 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Mon, 15 Jun 2026 14:37:55 +0700 Subject: [PATCH 1/9] Add `getBounds()` method to range values --- src/Expression/DateRangeValue.php | 13 +++++++++++++ src/Expression/Int4RangeValue.php | 13 +++++++++++++ src/Expression/Int8RangeValue.php | 13 +++++++++++++ src/Expression/NumRangeValue.php | 18 ++++++++++++++++++ src/Expression/TsRangeValue.php | 13 +++++++++++++ src/Expression/TsTzRangeValue.php | 13 +++++++++++++ 6 files changed, 83 insertions(+) diff --git a/src/Expression/DateRangeValue.php b/src/Expression/DateRangeValue.php index 9fa76d548..69da6f98f 100644 --- a/src/Expression/DateRangeValue.php +++ b/src/Expression/DateRangeValue.php @@ -15,4 +15,17 @@ public function __construct( public readonly bool $includeLower = true, public readonly bool $includeUpper = true, ) {} + + public function getBounds(): array + { + $lower = $this->lower === null || $this->includeLower + ? $this->lower + : $this->lower->modify('+1 day'); + + $upper = $this->upper === null || $this->includeUpper + ? $this->upper + : $this->upper->modify('-1 day'); + + return [$lower, $upper]; + } } diff --git a/src/Expression/Int4RangeValue.php b/src/Expression/Int4RangeValue.php index d65c6efde..755c64514 100644 --- a/src/Expression/Int4RangeValue.php +++ b/src/Expression/Int4RangeValue.php @@ -14,4 +14,17 @@ public function __construct( public readonly bool $includeLower = true, public readonly bool $includeUpper = true, ) {} + + public function getBounds(): array + { + $lower = $this->lower === null || $this->includeLower + ? $this->lower + : $this->lower + 1; + + $upper = $this->upper === null || $this->includeUpper + ? $this->upper + : $this->upper - 1; + + return [$lower, $upper]; + } } diff --git a/src/Expression/Int8RangeValue.php b/src/Expression/Int8RangeValue.php index 903425569..7a152f354 100644 --- a/src/Expression/Int8RangeValue.php +++ b/src/Expression/Int8RangeValue.php @@ -14,4 +14,17 @@ public function __construct( public readonly bool $includeLower = true, public readonly bool $includeUpper = true, ) {} + + public function getBounds(): array + { + $lower = $this->lower === null || $this->includeLower + ? $this->lower + : $this->lower + 1; + + $upper = $this->upper === null || $this->includeUpper + ? $this->upper + : $this->upper - 1; + + return [$lower, $upper]; + } } diff --git a/src/Expression/NumRangeValue.php b/src/Expression/NumRangeValue.php index 1f5d3f234..397164092 100644 --- a/src/Expression/NumRangeValue.php +++ b/src/Expression/NumRangeValue.php @@ -5,6 +5,7 @@ namespace Yiisoft\Db\Pgsql\Expression; use Yiisoft\Db\Expression\ExpressionInterface; +use RuntimeException; final class NumRangeValue implements ExpressionInterface { @@ -14,4 +15,21 @@ public function __construct( public readonly bool $includeLower = true, public readonly bool $includeUpper = true, ) {} + + public function getBounds(): array + { + $lower = $this->lower === null || $this->includeLower + ? $this->lower + : throw new RuntimeException( + 'Lower bound cannot be determined from the excluded values of a numeric range.', + ); + + $upper = $this->upper === null || $this->includeUpper + ? $this->upper + : throw new RuntimeException( + 'Upper bound cannot be determined from the excluded values of a numeric range.', + ); + + return [$lower, $upper]; + } } diff --git a/src/Expression/TsRangeValue.php b/src/Expression/TsRangeValue.php index 50594c8e9..5c5afd44c 100644 --- a/src/Expression/TsRangeValue.php +++ b/src/Expression/TsRangeValue.php @@ -15,4 +15,17 @@ public function __construct( public readonly bool $includeLower = true, public readonly bool $includeUpper = true, ) {} + + public function getBounds(): array + { + $lower = $this->lower === null || $this->includeLower + ? $this->lower + : $this->lower->modify('+1 second'); + + $upper = $this->upper === null || $this->includeUpper + ? $this->upper + : $this->upper->modify('-1 second'); + + return [$lower, $upper]; + } } diff --git a/src/Expression/TsTzRangeValue.php b/src/Expression/TsTzRangeValue.php index 7f08c3aab..be9c85cf3 100644 --- a/src/Expression/TsTzRangeValue.php +++ b/src/Expression/TsTzRangeValue.php @@ -15,4 +15,17 @@ public function __construct( public readonly bool $includeLower = true, public readonly bool $includeUpper = true, ) {} + + public function getBounds(): array + { + $lower = $this->lower === null || $this->includeLower + ? $this->lower + : $this->lower->modify('+1 second'); + + $upper = $this->upper === null || $this->includeUpper + ? $this->upper + : $this->upper->modify('-1 second'); + + return [$lower, $upper]; + } } From 6cf8bfe98502b1f65add5ae0746be73686204354 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Mon, 15 Jun 2026 14:54:27 +0700 Subject: [PATCH 2/9] Improve --- src/Expression/Int8RangeValue.php | 16 ++++++++++++++-- src/Expression/NumRangeValue.php | 4 ++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Expression/Int8RangeValue.php b/src/Expression/Int8RangeValue.php index 7a152f354..b5ec48b36 100644 --- a/src/Expression/Int8RangeValue.php +++ b/src/Expression/Int8RangeValue.php @@ -5,6 +5,10 @@ namespace Yiisoft\Db\Pgsql\Expression; use Yiisoft\Db\Expression\ExpressionInterface; +use InvalidArgumentException; + +use const PHP_INT_MAX; +use const PHP_INT_MIN; final class Int8RangeValue implements ExpressionInterface { @@ -19,11 +23,19 @@ public function getBounds(): array { $lower = $this->lower === null || $this->includeLower ? $this->lower - : $this->lower + 1; + : ( + PHP_INT_MIN <= $this->lower && $this->lower < PHP_INT_MAX + ? (int) $this->lower + 1 + : throw new InvalidArgumentException('Lower bound cannot be determined from the excluded value of a bigint range.') + ); $upper = $this->upper === null || $this->includeUpper ? $this->upper - : $this->upper - 1; + : ( + PHP_INT_MIN < $this->upper && $this->upper <= PHP_INT_MAX + ? (int) $this->upper - 1 + : throw new InvalidArgumentException('Upper bound cannot be determined from the excluded value of a bigint range.') + ); return [$lower, $upper]; } diff --git a/src/Expression/NumRangeValue.php b/src/Expression/NumRangeValue.php index 397164092..25e44bd1c 100644 --- a/src/Expression/NumRangeValue.php +++ b/src/Expression/NumRangeValue.php @@ -21,13 +21,13 @@ public function getBounds(): array $lower = $this->lower === null || $this->includeLower ? $this->lower : throw new RuntimeException( - 'Lower bound cannot be determined from the excluded values of a numeric range.', + 'Lower bound cannot be determined from the excluded value of a numeric range.', ); $upper = $this->upper === null || $this->includeUpper ? $this->upper : throw new RuntimeException( - 'Upper bound cannot be determined from the excluded values of a numeric range.', + 'Upper bound cannot be determined from the excluded value of a numeric range.', ); return [$lower, $upper]; From 52adac652e7995863959c75ec68cd7058d96c671 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Mon, 15 Jun 2026 21:14:35 +0700 Subject: [PATCH 3/9] Add `RangeValueInterface` --- src/Column/AbstractMultiRangeColumn.php | 5 +++-- src/Column/AbstractRangeColumn.php | 7 +++++-- src/Expression/DateRangeValue.php | 6 ++++-- src/Expression/Int4RangeValue.php | 7 ++++--- src/Expression/Int8RangeValue.php | 6 ++++-- src/Expression/NumRangeValue.php | 6 ++++-- src/Expression/RangeValueInterface.php | 20 ++++++++++++++++++++ src/Expression/TsRangeValue.php | 6 ++++-- src/Expression/TsTzRangeValue.php | 6 ++++-- 9 files changed, 52 insertions(+), 17 deletions(-) create mode 100644 src/Expression/RangeValueInterface.php diff --git a/src/Column/AbstractMultiRangeColumn.php b/src/Column/AbstractMultiRangeColumn.php index 6125d3142..bfb71b2fc 100644 --- a/src/Column/AbstractMultiRangeColumn.php +++ b/src/Column/AbstractMultiRangeColumn.php @@ -7,6 +7,7 @@ use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Pgsql\Expression\MultiRangeValue; +use Yiisoft\Db\Pgsql\Expression\RangeValueInterface; use Yiisoft\Db\Schema\Column\AbstractColumn; use Yiisoft\Db\Schema\Column\ColumnInterface; @@ -15,7 +16,7 @@ use function is_string; /** - * @template T of ExpressionInterface + * @template T of RangeValueInterface */ abstract class AbstractMultiRangeColumn extends AbstractColumn { @@ -53,7 +54,7 @@ public function dbTypecast(mixed $value): mixed /** * @inheritDoc * - * @return ?ExpressionInterface[] + * @return ?RangeValueInterface[] * * @psalm-return ?T[] */ diff --git a/src/Column/AbstractRangeColumn.php b/src/Column/AbstractRangeColumn.php index 1c4a3fa8e..bcfa4bc4f 100644 --- a/src/Column/AbstractRangeColumn.php +++ b/src/Column/AbstractRangeColumn.php @@ -7,6 +7,7 @@ use InvalidArgumentException; use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Expression\ExpressionInterface; +use Yiisoft\Db\Pgsql\Expression\RangeValueInterface; use Yiisoft\Db\Schema\Column\AbstractColumn; use Yiisoft\Db\Schema\Column\ColumnInterface; @@ -16,7 +17,7 @@ use function sprintf; /** - * @template T of ExpressionInterface + * @template T of RangeValueInterface */ abstract class AbstractRangeColumn extends AbstractColumn { @@ -56,7 +57,7 @@ public function dbTypecast(mixed $value): mixed /** * @inheritDoc * - * @return ?ExpressionInterface + * @return ?RangeValueInterface * * @psalm-return ?T */ @@ -88,6 +89,8 @@ abstract protected function getBoundColumn(): ColumnInterface; /** * @throws NotSupportedException * + * @return RangeValueInterface + * * @psalm-return T */ abstract protected function createRangeValue( diff --git a/src/Expression/DateRangeValue.php b/src/Expression/DateRangeValue.php index 69da6f98f..f5c7e50d6 100644 --- a/src/Expression/DateRangeValue.php +++ b/src/Expression/DateRangeValue.php @@ -5,9 +5,11 @@ namespace Yiisoft\Db\Pgsql\Expression; use DateTimeImmutable; -use Yiisoft\Db\Expression\ExpressionInterface; -final class DateRangeValue implements ExpressionInterface +/** + * @implements RangeValueInterface + */ +final class DateRangeValue implements RangeValueInterface { public function __construct( public readonly ?DateTimeImmutable $lower = null, diff --git a/src/Expression/Int4RangeValue.php b/src/Expression/Int4RangeValue.php index 755c64514..4ca7e9f4e 100644 --- a/src/Expression/Int4RangeValue.php +++ b/src/Expression/Int4RangeValue.php @@ -4,9 +4,10 @@ namespace Yiisoft\Db\Pgsql\Expression; -use Yiisoft\Db\Expression\ExpressionInterface; - -final class Int4RangeValue implements ExpressionInterface +/** + * @implements RangeValueInterface + */ +final class Int4RangeValue implements RangeValueInterface { public function __construct( public readonly ?int $lower = null, diff --git a/src/Expression/Int8RangeValue.php b/src/Expression/Int8RangeValue.php index b5ec48b36..a4202f628 100644 --- a/src/Expression/Int8RangeValue.php +++ b/src/Expression/Int8RangeValue.php @@ -4,13 +4,15 @@ namespace Yiisoft\Db\Pgsql\Expression; -use Yiisoft\Db\Expression\ExpressionInterface; use InvalidArgumentException; use const PHP_INT_MAX; use const PHP_INT_MIN; -final class Int8RangeValue implements ExpressionInterface +/** + * @implements RangeValueInterface + */ +final class Int8RangeValue implements RangeValueInterface { public function __construct( public readonly int|string|null $lower = null, diff --git a/src/Expression/NumRangeValue.php b/src/Expression/NumRangeValue.php index 25e44bd1c..7c382a8f9 100644 --- a/src/Expression/NumRangeValue.php +++ b/src/Expression/NumRangeValue.php @@ -4,10 +4,12 @@ namespace Yiisoft\Db\Pgsql\Expression; -use Yiisoft\Db\Expression\ExpressionInterface; use RuntimeException; -final class NumRangeValue implements ExpressionInterface +/** + * @implements RangeValueInterface + */ +final class NumRangeValue implements RangeValueInterface { public function __construct( public readonly int|float|null $lower = null, diff --git a/src/Expression/RangeValueInterface.php b/src/Expression/RangeValueInterface.php new file mode 100644 index 000000000..4be955fdf --- /dev/null +++ b/src/Expression/RangeValueInterface.php @@ -0,0 +1,20 @@ + + */ +final class TsRangeValue implements RangeValueInterface { public function __construct( public readonly ?DateTimeImmutable $lower = null, diff --git a/src/Expression/TsTzRangeValue.php b/src/Expression/TsTzRangeValue.php index be9c85cf3..df77830ce 100644 --- a/src/Expression/TsTzRangeValue.php +++ b/src/Expression/TsTzRangeValue.php @@ -5,9 +5,11 @@ namespace Yiisoft\Db\Pgsql\Expression; use DateTimeImmutable; -use Yiisoft\Db\Expression\ExpressionInterface; -final class TsTzRangeValue implements ExpressionInterface +/** + * @implements RangeValueInterface + */ +final class TsTzRangeValue implements RangeValueInterface { public function __construct( public readonly ?DateTimeImmutable $lower = null, From 3eecd75b6730519f75d7e3e4c30398491aa5b6e9 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Mon, 15 Jun 2026 21:23:52 +0700 Subject: [PATCH 4/9] Improve --- src/Expression/Int8RangeValue.php | 10 +++++++--- src/Expression/RangeValueInterface.php | 4 ++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Expression/Int8RangeValue.php b/src/Expression/Int8RangeValue.php index a4202f628..b638d85a4 100644 --- a/src/Expression/Int8RangeValue.php +++ b/src/Expression/Int8RangeValue.php @@ -4,7 +4,7 @@ namespace Yiisoft\Db\Pgsql\Expression; -use InvalidArgumentException; +use RuntimeException; use const PHP_INT_MAX; use const PHP_INT_MIN; @@ -28,7 +28,9 @@ public function getBounds(): array : ( PHP_INT_MIN <= $this->lower && $this->lower < PHP_INT_MAX ? (int) $this->lower + 1 - : throw new InvalidArgumentException('Lower bound cannot be determined from the excluded value of a bigint range.') + : throw new RuntimeException( + 'Lower bound cannot be determined from the excluded value of a bigint range.' + ) ); $upper = $this->upper === null || $this->includeUpper @@ -36,7 +38,9 @@ public function getBounds(): array : ( PHP_INT_MIN < $this->upper && $this->upper <= PHP_INT_MAX ? (int) $this->upper - 1 - : throw new InvalidArgumentException('Upper bound cannot be determined from the excluded value of a bigint range.') + : throw new RuntimeException( + 'Upper bound cannot be determined from the excluded value of a bigint range.' + ) ); return [$lower, $upper]; diff --git a/src/Expression/RangeValueInterface.php b/src/Expression/RangeValueInterface.php index 4be955fdf..e05e13196 100644 --- a/src/Expression/RangeValueInterface.php +++ b/src/Expression/RangeValueInterface.php @@ -5,8 +5,12 @@ namespace Yiisoft\Db\Pgsql\Expression; use Yiisoft\Db\Expression\ExpressionInterface; +use Yiisoft\Db\Pgsql\Column\AbstractMultiRangeColumn; +use Yiisoft\Db\Pgsql\Column\AbstractRangeColumn; /** + * Represents a range value of {@see AbstractRangeColumn} and {@see AbstractMultiRangeColumn} column types. + * * @template T */ interface RangeValueInterface extends ExpressionInterface From 077bf732a87bf6d4bce8c9f28b0aa2adc6e0c40e Mon Sep 17 00:00:00 2001 From: Tigrov <8563175+Tigrov@users.noreply.github.com> Date: Mon, 15 Jun 2026 14:25:06 +0000 Subject: [PATCH 5/9] Apply PHP CS Fixer and Rector changes (CI) --- src/Expression/Int8RangeValue.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Expression/Int8RangeValue.php b/src/Expression/Int8RangeValue.php index b638d85a4..5cc50e9b9 100644 --- a/src/Expression/Int8RangeValue.php +++ b/src/Expression/Int8RangeValue.php @@ -29,7 +29,7 @@ public function getBounds(): array PHP_INT_MIN <= $this->lower && $this->lower < PHP_INT_MAX ? (int) $this->lower + 1 : throw new RuntimeException( - 'Lower bound cannot be determined from the excluded value of a bigint range.' + 'Lower bound cannot be determined from the excluded value of a bigint range.', ) ); @@ -39,7 +39,7 @@ public function getBounds(): array PHP_INT_MIN < $this->upper && $this->upper <= PHP_INT_MAX ? (int) $this->upper - 1 : throw new RuntimeException( - 'Upper bound cannot be determined from the excluded value of a bigint range.' + 'Upper bound cannot be determined from the excluded value of a bigint range.', ) ); From 248c7efd7c06882fd36e304fcbc9b0bac4793c12 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Wed, 17 Jun 2026 13:34:45 +0700 Subject: [PATCH 6/9] Remove `RangeValueInterface` --- src/Column/AbstractMultiRangeColumn.php | 5 ++--- src/Column/AbstractRangeColumn.php | 7 ++----- src/Expression/DateRangeValue.php | 11 +++++++---- src/Expression/Int4RangeValue.php | 12 ++++++++---- src/Expression/Int8RangeValue.php | 11 +++++++---- src/Expression/NumRangeValue.php | 11 +++++++---- src/Expression/RangeValueInterface.php | 24 ------------------------ src/Expression/TsRangeValue.php | 11 +++++++---- src/Expression/TsTzRangeValue.php | 11 +++++++---- 9 files changed, 47 insertions(+), 56 deletions(-) delete mode 100644 src/Expression/RangeValueInterface.php diff --git a/src/Column/AbstractMultiRangeColumn.php b/src/Column/AbstractMultiRangeColumn.php index bfb71b2fc..6125d3142 100644 --- a/src/Column/AbstractMultiRangeColumn.php +++ b/src/Column/AbstractMultiRangeColumn.php @@ -7,7 +7,6 @@ use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Pgsql\Expression\MultiRangeValue; -use Yiisoft\Db\Pgsql\Expression\RangeValueInterface; use Yiisoft\Db\Schema\Column\AbstractColumn; use Yiisoft\Db\Schema\Column\ColumnInterface; @@ -16,7 +15,7 @@ use function is_string; /** - * @template T of RangeValueInterface + * @template T of ExpressionInterface */ abstract class AbstractMultiRangeColumn extends AbstractColumn { @@ -54,7 +53,7 @@ public function dbTypecast(mixed $value): mixed /** * @inheritDoc * - * @return ?RangeValueInterface[] + * @return ?ExpressionInterface[] * * @psalm-return ?T[] */ diff --git a/src/Column/AbstractRangeColumn.php b/src/Column/AbstractRangeColumn.php index bcfa4bc4f..1c4a3fa8e 100644 --- a/src/Column/AbstractRangeColumn.php +++ b/src/Column/AbstractRangeColumn.php @@ -7,7 +7,6 @@ use InvalidArgumentException; use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Expression\ExpressionInterface; -use Yiisoft\Db\Pgsql\Expression\RangeValueInterface; use Yiisoft\Db\Schema\Column\AbstractColumn; use Yiisoft\Db\Schema\Column\ColumnInterface; @@ -17,7 +16,7 @@ use function sprintf; /** - * @template T of RangeValueInterface + * @template T of ExpressionInterface */ abstract class AbstractRangeColumn extends AbstractColumn { @@ -57,7 +56,7 @@ public function dbTypecast(mixed $value): mixed /** * @inheritDoc * - * @return ?RangeValueInterface + * @return ?ExpressionInterface * * @psalm-return ?T */ @@ -89,8 +88,6 @@ abstract protected function getBoundColumn(): ColumnInterface; /** * @throws NotSupportedException * - * @return RangeValueInterface - * * @psalm-return T */ abstract protected function createRangeValue( diff --git a/src/Expression/DateRangeValue.php b/src/Expression/DateRangeValue.php index f5c7e50d6..8e4955427 100644 --- a/src/Expression/DateRangeValue.php +++ b/src/Expression/DateRangeValue.php @@ -5,11 +5,9 @@ namespace Yiisoft\Db\Pgsql\Expression; use DateTimeImmutable; +use Yiisoft\Db\Expression\ExpressionInterface; -/** - * @implements RangeValueInterface - */ -final class DateRangeValue implements RangeValueInterface +final class DateRangeValue implements ExpressionInterface { public function __construct( public readonly ?DateTimeImmutable $lower = null, @@ -18,6 +16,11 @@ public function __construct( public readonly bool $includeUpper = true, ) {} + /** + * Returns the lower and upper bounds of a range, inclusive. + * + * @psalm-return array{0: ?DateTimeImmutable, 1: ?DateTimeImmutable} + */ public function getBounds(): array { $lower = $this->lower === null || $this->includeLower diff --git a/src/Expression/Int4RangeValue.php b/src/Expression/Int4RangeValue.php index 4ca7e9f4e..1ff9ab9cc 100644 --- a/src/Expression/Int4RangeValue.php +++ b/src/Expression/Int4RangeValue.php @@ -4,10 +4,9 @@ namespace Yiisoft\Db\Pgsql\Expression; -/** - * @implements RangeValueInterface - */ -final class Int4RangeValue implements RangeValueInterface +use Yiisoft\Db\Expression\ExpressionInterface; + +final class Int4RangeValue implements ExpressionInterface { public function __construct( public readonly ?int $lower = null, @@ -16,6 +15,11 @@ public function __construct( public readonly bool $includeUpper = true, ) {} + /** + * Returns the lower and upper bounds of a range, inclusive. + * + * @psalm-return array{0: ?int, 1: ?int} + */ public function getBounds(): array { $lower = $this->lower === null || $this->includeLower diff --git a/src/Expression/Int8RangeValue.php b/src/Expression/Int8RangeValue.php index 5cc50e9b9..2977c3f60 100644 --- a/src/Expression/Int8RangeValue.php +++ b/src/Expression/Int8RangeValue.php @@ -5,14 +5,12 @@ namespace Yiisoft\Db\Pgsql\Expression; use RuntimeException; +use Yiisoft\Db\Expression\ExpressionInterface; use const PHP_INT_MAX; use const PHP_INT_MIN; -/** - * @implements RangeValueInterface - */ -final class Int8RangeValue implements RangeValueInterface +final class Int8RangeValue implements ExpressionInterface { public function __construct( public readonly int|string|null $lower = null, @@ -21,6 +19,11 @@ public function __construct( public readonly bool $includeUpper = true, ) {} + /** + * Returns the lower and upper bounds of a range, inclusive. + * + * @psalm-return array{0: int|string|null, 1: int|string|null} + */ public function getBounds(): array { $lower = $this->lower === null || $this->includeLower diff --git a/src/Expression/NumRangeValue.php b/src/Expression/NumRangeValue.php index 7c382a8f9..341ae14cc 100644 --- a/src/Expression/NumRangeValue.php +++ b/src/Expression/NumRangeValue.php @@ -5,11 +5,9 @@ namespace Yiisoft\Db\Pgsql\Expression; use RuntimeException; +use Yiisoft\Db\Expression\ExpressionInterface; -/** - * @implements RangeValueInterface - */ -final class NumRangeValue implements RangeValueInterface +final class NumRangeValue implements ExpressionInterface { public function __construct( public readonly int|float|null $lower = null, @@ -18,6 +16,11 @@ public function __construct( public readonly bool $includeUpper = true, ) {} + /** + * Returns the lower and upper bounds of a range, inclusive. + * + * @psalm-return array{0: int|float|null, 1: int|float|null} + */ public function getBounds(): array { $lower = $this->lower === null || $this->includeLower diff --git a/src/Expression/RangeValueInterface.php b/src/Expression/RangeValueInterface.php deleted file mode 100644 index e05e13196..000000000 --- a/src/Expression/RangeValueInterface.php +++ /dev/null @@ -1,24 +0,0 @@ - - */ -final class TsRangeValue implements RangeValueInterface +final class TsRangeValue implements ExpressionInterface { public function __construct( public readonly ?DateTimeImmutable $lower = null, @@ -18,6 +16,11 @@ public function __construct( public readonly bool $includeUpper = true, ) {} + /** + * Returns the lower and upper bounds of a range, inclusive. + * + * @psalm-return array{0: ?DateTimeImmutable, 1: ?DateTimeImmutable} + */ public function getBounds(): array { $lower = $this->lower === null || $this->includeLower diff --git a/src/Expression/TsTzRangeValue.php b/src/Expression/TsTzRangeValue.php index df77830ce..0112d6cbf 100644 --- a/src/Expression/TsTzRangeValue.php +++ b/src/Expression/TsTzRangeValue.php @@ -5,11 +5,9 @@ namespace Yiisoft\Db\Pgsql\Expression; use DateTimeImmutable; +use Yiisoft\Db\Expression\ExpressionInterface; -/** - * @implements RangeValueInterface - */ -final class TsTzRangeValue implements RangeValueInterface +final class TsTzRangeValue implements ExpressionInterface { public function __construct( public readonly ?DateTimeImmutable $lower = null, @@ -18,6 +16,11 @@ public function __construct( public readonly bool $includeUpper = true, ) {} + /** + * Returns the lower and upper bounds of a range, inclusive. + * + * @psalm-return array{0: ?DateTimeImmutable, 1: ?DateTimeImmutable} + */ public function getBounds(): array { $lower = $this->lower === null || $this->includeLower From ffeaa711ae8970cb7fada3efb430d7787d76203d Mon Sep 17 00:00:00 2001 From: Tigrov Date: Wed, 17 Jun 2026 14:05:31 +0700 Subject: [PATCH 7/9] Add tests --- src/Expression/Int8RangeValue.php | 4 +- tests/RangeValueTest.php | 145 ++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 tests/RangeValueTest.php diff --git a/src/Expression/Int8RangeValue.php b/src/Expression/Int8RangeValue.php index 2977c3f60..6d017f150 100644 --- a/src/Expression/Int8RangeValue.php +++ b/src/Expression/Int8RangeValue.php @@ -29,7 +29,7 @@ public function getBounds(): array $lower = $this->lower === null || $this->includeLower ? $this->lower : ( - PHP_INT_MIN <= $this->lower && $this->lower < PHP_INT_MAX + PHP_INT_MIN < $this->lower && $this->lower < PHP_INT_MAX ? (int) $this->lower + 1 : throw new RuntimeException( 'Lower bound cannot be determined from the excluded value of a bigint range.', @@ -39,7 +39,7 @@ public function getBounds(): array $upper = $this->upper === null || $this->includeUpper ? $this->upper : ( - PHP_INT_MIN < $this->upper && $this->upper <= PHP_INT_MAX + PHP_INT_MIN < $this->upper && $this->upper < PHP_INT_MAX ? (int) $this->upper - 1 : throw new RuntimeException( 'Upper bound cannot be determined from the excluded value of a bigint range.', diff --git a/tests/RangeValueTest.php b/tests/RangeValueTest.php new file mode 100644 index 000000000..13bb568f7 --- /dev/null +++ b/tests/RangeValueTest.php @@ -0,0 +1,145 @@ + [ + new Int4RangeValue(1, 5), + [1, 5], + ]; + yield 'int4 exclusive' => [ + new Int4RangeValue(1, 5, false, false), + [2, 4], + ]; + yield 'int4 unbounded' => [ + new Int4RangeValue(null, null, false, false), + [null, null], + ]; + yield 'int8 inclusive string bounds' => [ + new Int8RangeValue(PHP_INT_MIN, PHP_INT_MAX), + [PHP_INT_MIN, PHP_INT_MAX], + ]; + yield 'int8 exclusive' => [ + new Int8RangeValue('1', '5', false, false), + [2, 4], + ]; + yield 'int8 unbounded' => [ + new Int8RangeValue(null, null, false, false), + [null, null], + ]; + yield 'num inclusive' => [ + new NumRangeValue(1.5, 5.5), + [1.5, 5.5], + ]; + yield 'num unbounded' => [ + new NumRangeValue(null, null, false, false), + [null, null], + ]; + yield 'date inclusive' => [ + new DateRangeValue(new DateTimeImmutable('2024-01-01'), new DateTimeImmutable('2024-01-05')), + [new DateTimeImmutable('2024-01-01'), new DateTimeImmutable('2024-01-05')], + ]; + yield 'date exclusive' => [ + new DateRangeValue( + new DateTimeImmutable('2024-01-01'), + new DateTimeImmutable('2024-01-05'), + false, + false, + ), + [new DateTimeImmutable('2024-01-02'), new DateTimeImmutable('2024-01-04')], + ]; + yield 'timestamp exclusive' => [ + new TsRangeValue( + new DateTimeImmutable('2024-01-01 10:00:00'), + new DateTimeImmutable('2024-01-01 15:00:00'), + false, + false, + ), + [new DateTimeImmutable('2024-01-01 10:00:01'), new DateTimeImmutable('2024-01-01 14:59:59')], + ]; + yield 'timestamp with time zone exclusive' => [ + new TsTzRangeValue( + new DateTimeImmutable('2024-01-01 10:00:00+02:00'), + new DateTimeImmutable('2024-01-01 15:00:00+02:00'), + false, + false, + ), + [new DateTimeImmutable('2024-01-01 10:00:01+02:00'), new DateTimeImmutable('2024-01-01 14:59:59+02:00')], + ]; + } + + #[DataProvider('boundsProvider')] + public function testGetBounds(object $rangeValue, array $expected): void + { + $this->assertBoundsSame($expected, $rangeValue->getBounds()); + } + + public static function boundsExceptionProvider(): iterable + { + yield 'int8 lower below minimum' => [ + new Int8RangeValue(PHP_INT_MIN, null, false), + 'Lower bound cannot be determined from the excluded value of a bigint range.', + ]; + yield 'int8 lower at maximum' => [ + new Int8RangeValue(PHP_INT_MAX, null, false), + 'Lower bound cannot be determined from the excluded value of a bigint range.', + ]; + yield 'int8 upper above maximum' => [ + new Int8RangeValue(null, PHP_INT_MAX, true, false), + 'Upper bound cannot be determined from the excluded value of a bigint range.', + ]; + yield 'int8 upper at minimum' => [ + new Int8RangeValue(null, PHP_INT_MIN, true, false), + 'Upper bound cannot be determined from the excluded value of a bigint range.', + ]; + yield 'num lower exclusive' => [ + new NumRangeValue(1.5, null, false), + 'Lower bound cannot be determined from the excluded value of a numeric range.', + ]; + yield 'num upper exclusive' => [ + new NumRangeValue(null, 5.5, true, false), + 'Upper bound cannot be determined from the excluded value of a numeric range.', + ]; + } + + #[DataProvider('boundsExceptionProvider')] + public function testGetBoundsException(object $rangeValue, string $expectedMessage): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage($expectedMessage); + + $rangeValue->getBounds(); + } + + private function assertBoundsSame(array $expected, array $actual): void + { + $this->assertCount(2, $actual); + + foreach ($expected as $key => $expectedValue) { + if ($expectedValue instanceof DateTimeImmutable) { + $this->assertEquals($expectedValue, $actual[$key]); + } else { + $this->assertSame($expectedValue, $actual[$key]); + } + } + } +} From 6e654b54460b3b867e00c06cea19b669fe714e4f Mon Sep 17 00:00:00 2001 From: Tigrov Date: Wed, 17 Jun 2026 14:10:06 +0700 Subject: [PATCH 8/9] Add line to CHANGELOG.md [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eea3494e..7792ed168 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Chg #483: Bump `yiisoft/db` version to `^2.0.1` (@vjik) - Bug #484: Fix building range-type column definitions (@Tigrov) - Enh #489: Clarify return type of `phpTypecast()` methods for range columns (@Tigrov) +- New #492: Add `getBounds()` method to range values (@Tigrov) ## 2.0.1 February 07, 2026 From 29ded83498658ac84b95630e6cb420701b8cb73a Mon Sep 17 00:00:00 2001 From: Tigrov Date: Wed, 17 Jun 2026 14:38:51 +0700 Subject: [PATCH 9/9] Update test data [skip ci] --- tests/RangeValueTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/RangeValueTest.php b/tests/RangeValueTest.php index 13bb568f7..cdf264022 100644 --- a/tests/RangeValueTest.php +++ b/tests/RangeValueTest.php @@ -34,7 +34,7 @@ public static function boundsProvider(): iterable new Int4RangeValue(null, null, false, false), [null, null], ]; - yield 'int8 inclusive string bounds' => [ + yield 'int8 inclusive beyond bounds' => [ new Int8RangeValue(PHP_INT_MIN, PHP_INT_MAX), [PHP_INT_MIN, PHP_INT_MAX], ];