From 4099ba3c4b27fea90f89b26b59be0896869fefd4 Mon Sep 17 00:00:00 2001 From: Jan Klan Date: Wed, 7 May 2025 11:08:39 +0930 Subject: [PATCH] Add the ability to specify range bounds --- README.md | 3 ++ .../Query/AST/Functions/BaseRangeFunction.php | 33 +++++++++++++++++++ .../ORM/Query/AST/Functions/Daterange.php | 17 +++++++--- .../ORM/Query/AST/Functions/Int4range.php | 8 ++--- .../ORM/Query/AST/Functions/Int8range.php | 8 ++--- .../ORM/Query/AST/Functions/Numrange.php | 8 ++--- .../ORM/Query/AST/Functions/Tsrange.php | 17 +++++++--- .../ORM/Query/AST/Functions/Tstzrange.php | 17 +++++++--- .../ORM/Query/AST/Functions/Int4rangeTest.php | 2 ++ .../ORM/Query/AST/Functions/Int8rangeTest.php | 2 ++ .../ORM/Query/AST/Functions/NumrangeTest.php | 2 ++ .../ORM/Query/AST/Functions/TsrangeTest.php | 2 ++ .../ORM/Query/AST/Functions/TstzrangeTest.php | 2 ++ 13 files changed, 91 insertions(+), 30 deletions(-) create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseRangeFunction.php diff --git a/README.md b/README.md index 4c930f11..69de99a9 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,9 @@ This package provides comprehensive Doctrine support for PostgreSQL features: - Array aggregation (`array_agg`) - Array manipulation (`array_append`, `array_prepend`, `array_remove`, `array_replace`, `array_shuffle`) - Array dimensions and length +- **Range functions** + - Arithmetic ranges (`int4range`, `int8range`, `numrange`) + - Date ranges (`daterange`, `tsrange`, `tstzrange`) - **JSON Functions** - JSON construction (`json_build_object`, `jsonb_build_object`) - JSON manipulation and transformation diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseRangeFunction.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseRangeFunction.php new file mode 100644 index 00000000..dccbcf25 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseRangeFunction.php @@ -0,0 +1,33 @@ + + */ +abstract class BaseRangeFunction extends BaseVariadicFunction +{ + protected function getNodeMappingPattern(): array + { + return [ + 'ArithmeticPrimary', + 'ArithmeticPrimary', + 'StringPrimary', + ]; + } + + protected function getMinArgumentCount(): int + { + return 2; + } + + protected function getMaxArgumentCount(): int + { + return 3; + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Daterange.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Daterange.php index a6c1c1de..3909d853 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Daterange.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Daterange.php @@ -12,12 +12,19 @@ * * @author Martin Georgiev */ -class Daterange extends BaseFunction +class Daterange extends BaseRangeFunction { - protected function customizeFunction(): void + protected function getFunctionName(): string { - $this->setFunctionPrototype('daterange(%s, %s)'); - $this->addNodeMapping('StringPrimary'); - $this->addNodeMapping('StringPrimary'); + return 'daterange'; + } + + protected function getNodeMappingPattern(): array + { + return [ + 'StringPrimary', + 'StringPrimary', + 'StringPrimary', + ]; } } diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Int4range.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Int4range.php index 914acc27..59fa8272 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Int4range.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Int4range.php @@ -12,12 +12,10 @@ * * @author Martin Georgiev */ -class Int4range extends BaseFunction +class Int4range extends BaseRangeFunction { - protected function customizeFunction(): void + protected function getFunctionName(): string { - $this->setFunctionPrototype('int4range(%s, %s)'); - $this->addNodeMapping('StringPrimary'); - $this->addNodeMapping('StringPrimary'); + return 'int4range'; } } diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Int8range.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Int8range.php index 17ff66e4..4e0bb6d5 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Int8range.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Int8range.php @@ -12,12 +12,10 @@ * * @author Martin Georgiev */ -class Int8range extends BaseFunction +class Int8range extends BaseRangeFunction { - protected function customizeFunction(): void + protected function getFunctionName(): string { - $this->setFunctionPrototype('int8range(%s, %s)'); - $this->addNodeMapping('StringPrimary'); - $this->addNodeMapping('StringPrimary'); + return 'int8range'; } } diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Numrange.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Numrange.php index 6c0a22c4..db12d0e2 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Numrange.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Numrange.php @@ -12,12 +12,10 @@ * * @author Martin Georgiev */ -class Numrange extends BaseFunction +class Numrange extends BaseRangeFunction { - protected function customizeFunction(): void + protected function getFunctionName(): string { - $this->setFunctionPrototype('numrange(%s, %s)'); - $this->addNodeMapping('StringPrimary'); - $this->addNodeMapping('StringPrimary'); + return 'numrange'; } } diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Tsrange.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Tsrange.php index 5e2e2b33..9545fa96 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Tsrange.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Tsrange.php @@ -12,12 +12,19 @@ * * @author Martin Georgiev */ -class Tsrange extends BaseFunction +class Tsrange extends BaseRangeFunction { - protected function customizeFunction(): void + protected function getFunctionName(): string { - $this->setFunctionPrototype('tsrange(%s, %s)'); - $this->addNodeMapping('StringPrimary'); - $this->addNodeMapping('StringPrimary'); + return 'tsrange'; + } + + protected function getNodeMappingPattern(): array + { + return [ + 'StringPrimary', + 'StringPrimary', + 'StringPrimary', + ]; } } diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Tstzrange.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Tstzrange.php index 0289dab0..abb66359 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Tstzrange.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Tstzrange.php @@ -12,12 +12,19 @@ * * @author Martin Georgiev */ -class Tstzrange extends BaseFunction +class Tstzrange extends BaseRangeFunction { - protected function customizeFunction(): void + protected function getFunctionName(): string { - $this->setFunctionPrototype('tstzrange(%s, %s)'); - $this->addNodeMapping('StringPrimary'); - $this->addNodeMapping('StringPrimary'); + return 'tstzrange'; + } + + protected function getNodeMappingPattern(): array + { + return [ + 'StringPrimary', + 'StringPrimary', + 'StringPrimary', + ]; } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Int4rangeTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Int4rangeTest.php index 49de6921..447fe5d0 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Int4rangeTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Int4rangeTest.php @@ -20,6 +20,7 @@ protected function getExpectedSqlStatements(): array { return [ 'SELECT int4range(c0_.integer1, c0_.integer2) AS sclr_0 FROM ContainsIntegers c0_', + 'SELECT int4range(c0_.integer1, c0_.integer2, \'[)\') AS sclr_0 FROM ContainsIntegers c0_', ]; } @@ -27,6 +28,7 @@ protected function getDqlStatements(): array { return [ \sprintf('SELECT INT4RANGE(e.integer1, e.integer2) FROM %s e', ContainsIntegers::class), + \sprintf('SELECT INT4RANGE(e.integer1, e.integer2, \'[)\') FROM %s e', ContainsIntegers::class), ]; } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Int8rangeTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Int8rangeTest.php index e580d0ce..9b7246e3 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Int8rangeTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Int8rangeTest.php @@ -20,6 +20,7 @@ protected function getExpectedSqlStatements(): array { return [ 'SELECT int8range(c0_.integer1, c0_.integer2) AS sclr_0 FROM ContainsIntegers c0_', + 'SELECT int8range(c0_.integer1, c0_.integer2, \'[)\') AS sclr_0 FROM ContainsIntegers c0_', ]; } @@ -27,6 +28,7 @@ protected function getDqlStatements(): array { return [ \sprintf('SELECT INT8RANGE(e.integer1, e.integer2) FROM %s e', ContainsIntegers::class), + \sprintf('SELECT INT8RANGE(e.integer1, e.integer2, \'[)\') FROM %s e', ContainsIntegers::class), ]; } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/NumrangeTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/NumrangeTest.php index ea702025..6803fdce 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/NumrangeTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/NumrangeTest.php @@ -20,6 +20,7 @@ protected function getExpectedSqlStatements(): array { return [ 'SELECT numrange(c0_.decimal1, c0_.decimal2) AS sclr_0 FROM ContainsDecimals c0_', + 'SELECT numrange(c0_.decimal1, c0_.decimal2, \'[)\') AS sclr_0 FROM ContainsDecimals c0_', ]; } @@ -27,6 +28,7 @@ protected function getDqlStatements(): array { return [ \sprintf('SELECT NUMRANGE(e.decimal1, e.decimal2) FROM %s e', ContainsDecimals::class), + \sprintf('SELECT NUMRANGE(e.decimal1, e.decimal2, \'[)\') FROM %s e', ContainsDecimals::class), ]; } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TsrangeTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TsrangeTest.php index a4b95911..8eeb17d3 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TsrangeTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TsrangeTest.php @@ -20,6 +20,7 @@ protected function getExpectedSqlStatements(): array { return [ 'SELECT tsrange(c0_.datetime1, c0_.datetime2) AS sclr_0 FROM ContainsDates c0_', + 'SELECT tsrange(c0_.datetime1, c0_.datetime2, \'[)\') AS sclr_0 FROM ContainsDates c0_', ]; } @@ -27,6 +28,7 @@ protected function getDqlStatements(): array { return [ \sprintf('SELECT TSRANGE(e.datetime1, e.datetime2) FROM %s e', ContainsDates::class), + \sprintf('SELECT TSRANGE(e.datetime1, e.datetime2, \'[)\') FROM %s e', ContainsDates::class), ]; } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TstzrangeTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TstzrangeTest.php index 7abaffd3..094bbd53 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TstzrangeTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/TstzrangeTest.php @@ -20,6 +20,7 @@ protected function getExpectedSqlStatements(): array { return [ 'SELECT tstzrange(c0_.datetimetz1, c0_.datetimetz2) AS sclr_0 FROM ContainsDates c0_', + 'SELECT tstzrange(c0_.datetimetz1, c0_.datetimetz2, \'[)\') AS sclr_0 FROM ContainsDates c0_', ]; } @@ -27,6 +28,7 @@ protected function getDqlStatements(): array { return [ \sprintf('SELECT TSTZRANGE(e.datetimetz1, e.datetimetz2) FROM %s e', ContainsDates::class), + \sprintf('SELECT TSTZRANGE(e.datetimetz1, e.datetimetz2, \'[)\') FROM %s e', ContainsDates::class), ]; } }