From c533b42d541531cfbaf844b842f2d4175b6ea0ae Mon Sep 17 00:00:00 2001 From: Andrei Karpilin Date: Fri, 23 May 2025 14:24:15 +0100 Subject: [PATCH 1/5] feat: add support for formatting functions 'to_char', 'to_date', 'to_number', 'to_timestamp' --- README.md | 1 + docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md | 4 +++ docs/INTEGRATING-WITH-DOCTRINE.md | 6 ++++ docs/INTEGRATING-WITH-LARAVEL.md | 6 ++++ docs/INTEGRATING-WITH-SYMFONY.md | 6 ++++ .../ORM/Query/AST/Functions/ToChar.php | 20 +++++++++++ .../ORM/Query/AST/Functions/ToDate.php | 20 +++++++++++ .../ORM/Query/AST/Functions/ToNumber.php | 20 +++++++++++ .../ORM/Query/AST/Functions/ToTimestamp.php | 20 +++++++++++ .../ORM/Query/AST/Functions/ToCharTest.php | 24 +++++++++++++ .../ORM/Query/AST/Functions/ToDateTest.php | 24 +++++++++++++ .../ORM/Query/AST/Functions/ToNumberTest.php | 24 +++++++++++++ .../Query/AST/Functions/ToTimestampTest.php | 24 +++++++++++++ .../ORM/Query/AST/Functions/ToCharTest.php | 35 +++++++++++++++++++ .../ORM/Query/AST/Functions/ToDateTest.php | 32 +++++++++++++++++ .../ORM/Query/AST/Functions/ToNumberTest.php | 32 +++++++++++++++++ .../Query/AST/Functions/ToTimestampTest.php | 32 +++++++++++++++++ 17 files changed, 330 insertions(+) create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToChar.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDate.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumber.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestamp.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php diff --git a/README.md b/README.md index 27a2bd87..65152e60 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ This package provides comprehensive Doctrine support for PostgreSQL features: - Special aggregates (`any_value`, `xmlagg`) - **Mathematical/Arithmetic Functions** - **Range Functions** +- **Data Type Formatting Functions** Full documentation: - [Available Types](docs/AVAILABLE-TYPES.md) diff --git a/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md b/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md index b5bd2b60..ad9962eb 100644 --- a/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md +++ b/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md @@ -107,8 +107,12 @@ | starts_with | STARTS_WITH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StartsWith` | | string_agg | STRING_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StringAgg` | | string_to_array | STRING_TO_ARRAY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StringToArray` | +| to_char | TO_CHAR | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToChar` | +| to_date | TO_DATE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToDate` | | to_json | TO_JSON | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToJson` | | to_jsonb | TO_JSONB | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToJsonb` | +| to_number | TO_NUMBER | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToNumber` | +| to_timestamp | TO_TIMESTAMP | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTimestamp` | | to_tsquery | TO_TSQUERY | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTsquery` | | to_tsvector | TO_TSVECTOR | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTsvector` | | trunc | TRUNC | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Trunc` | diff --git a/docs/INTEGRATING-WITH-DOCTRINE.md b/docs/INTEGRATING-WITH-DOCTRINE.md index a72e8b99..0f1cd782 100644 --- a/docs/INTEGRATING-WITH-DOCTRINE.md +++ b/docs/INTEGRATING-WITH-DOCTRINE.md @@ -193,6 +193,12 @@ $configuration->addCustomStringFunction('JSONB_OBJECT_AGG', MartinGeorgiev\Doctr $configuration->addCustomStringFunction('STRING_AGG', MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StringAgg::class); $configuration->addCustomStringFunction('XML_AGG', MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\XmlAgg::class); +# data type formatting functions +$configuration->addCustomStringFunction('TO_CHAR', MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToChar::class); +$configuration->addCustomStringFunction('TO_DATE', MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToDate::class); +$configuration->addCustomStringFunction('TO_NUMBER', MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToNumber::class); +$configuration->addCustomStringFunction('TO_TIMESTAMP', MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTimestamp::class); + $em = EntityManager::create($dbParams, $configuration); ``` diff --git a/docs/INTEGRATING-WITH-LARAVEL.md b/docs/INTEGRATING-WITH-LARAVEL.md index 02372849..1a2fd2e3 100644 --- a/docs/INTEGRATING-WITH-LARAVEL.md +++ b/docs/INTEGRATING-WITH-LARAVEL.md @@ -247,6 +247,12 @@ return [ 'JSONB_OBJECT_AGG' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbObjectAgg::class, 'STRING_AGG' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StringAgg::class, 'XML_AGG' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\XmlAgg::class, + + # data type formatting functions + 'TO_CHAR' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToChar::class, + 'TO_DATE' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToDate::class, + 'TO_NUMBER' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToNumber::class, + 'TO_TIMESTAMP' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTimestamp::class, ], ... diff --git a/docs/INTEGRATING-WITH-SYMFONY.md b/docs/INTEGRATING-WITH-SYMFONY.md index e59224bc..784ccb1f 100644 --- a/docs/INTEGRATING-WITH-SYMFONY.md +++ b/docs/INTEGRATING-WITH-SYMFONY.md @@ -241,4 +241,10 @@ doctrine: JSONB_OBJECT_AGG: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbObjectAgg STRING_AGG: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StringAgg XML_AGG: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\XmlAgg + + # data type formatting functions + TO_CHAR: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToChar + TO_DATE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToDate + TO_NUMBER: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToNumber + TO_TIMESTAMP: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTimestamp ``` diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToChar.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToChar.php new file mode 100644 index 00000000..aa697eea --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToChar.php @@ -0,0 +1,20 @@ +setFunctionPrototype('to_char(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDate.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDate.php new file mode 100644 index 00000000..02cf2cbb --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDate.php @@ -0,0 +1,20 @@ +setFunctionPrototype('to_date(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumber.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumber.php new file mode 100644 index 00000000..8bc59911 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumber.php @@ -0,0 +1,20 @@ +setFunctionPrototype('to_number(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestamp.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestamp.php new file mode 100644 index 00000000..0343f9c4 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestamp.php @@ -0,0 +1,20 @@ +setFunctionPrototype('to_timestamp(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php new file mode 100644 index 00000000..b2e5de19 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php @@ -0,0 +1,24 @@ + ToChar::class, + ]; + } + + public function test_tochar(): void + { + $dql = "SELECT to_char(t.datetimetz1, 'HH12:MI:SS') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsDates t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + static::assertSame('10:30:00', $result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php new file mode 100644 index 00000000..9c05fb18 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php @@ -0,0 +1,24 @@ + ToDate::class, + ]; + } + + public function test_todate(): void + { + $dql = "SELECT to_date('05 Dec 2000', 'DD Mon YYYY') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + static::assertSame('2000-12-05', $result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php new file mode 100644 index 00000000..1f1d3ef6 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php @@ -0,0 +1,24 @@ + ToNumber::class, + ]; + } + + public function test_tonumber(): void + { + $dql = "SELECT to_number('12,454.8-', '99G999D9S') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + static::assertSame('-12454.8', $result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php new file mode 100644 index 00000000..27f6cc78 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php @@ -0,0 +1,24 @@ + ToTimestamp::class, + ]; + } + + public function test_totimestamp(): void + { + $dql = "SELECT to_timestamp('05 Dec 2000', 'DD Mon YYYY') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + static::assertSame('2000-12-05 00:00:00+00', $result[0]['result']); + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php new file mode 100644 index 00000000..0e708103 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php @@ -0,0 +1,35 @@ + ToChar::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + "SELECT to_char(c0_.datetime1, 'HH12:MI:SS') AS sclr_0 FROM ContainsDates c0_", + "SELECT to_char(c0_.decimal1, '999D99S') AS sclr_0 FROM ContainsDecimals c0_", + ]; + } + + protected function getDqlStatements(): array + { + return [ + \sprintf("SELECT TO_CHAR(e.datetime1, 'HH12:MI:SS') FROM %s e", ContainsDates::class), + \sprintf("SELECT TO_CHAR(e.decimal1, '999D99S') FROM %s e", ContainsDecimals::class), + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php new file mode 100644 index 00000000..8ac71687 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php @@ -0,0 +1,32 @@ + ToDate::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + "SELECT to_date(c0_.text1, 'DD Mon YYYY') AS sclr_0 FROM ContainsTexts c0_", + ]; + } + + protected function getDqlStatements(): array + { + return [ + \sprintf("SELECT TO_DATE(e.text1, 'DD Mon YYYY') FROM %s e", ContainsTexts::class), + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php new file mode 100644 index 00000000..74c03266 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php @@ -0,0 +1,32 @@ + ToNumber::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + "SELECT to_number(c0_.text1, '99G999D9S') AS sclr_0 FROM ContainsTexts c0_", + ]; + } + + protected function getDqlStatements(): array + { + return [ + \sprintf("SELECT TO_NUMBER(e.text1, '99G999D9S') FROM %s e", ContainsTexts::class), + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php new file mode 100644 index 00000000..95393989 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php @@ -0,0 +1,32 @@ + ToTimestamp::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + "SELECT to_timestamp(c0_.text1, 'DD Mon YYYY') AS sclr_0 FROM ContainsTexts c0_", + ]; + } + + protected function getDqlStatements(): array + { + return [ + \sprintf("SELECT TO_TIMESTAMP(e.text1, 'DD Mon YYYY') FROM %s e", ContainsTexts::class), + ]; + } +} From 739c9195bd0fdbd429b184d4ed8c7557fbd2026c Mon Sep 17 00:00:00 2001 From: Andrei Karpilin Date: Tue, 27 May 2025 13:54:42 +0100 Subject: [PATCH 2/5] Adds more tests Adds more comprehensive tests for the `to_char` function, covering various data types and formats. This ensures the function correctly handles different scenarios, including numeric, timestamp, and interval types, and validates its behavior with valid and invalid inputs. Also adds tests for to_timestamp, to_date and to_number to check invalid inputs and formats --- .../Doctrine/Entity/ContainsDates.php | 3 + .../ORM/Query/AST/Functions/ToChar.php | 2 +- .../ORM/Query/AST/Functions/DateTestCase.php | 7 +- .../ORM/Query/AST/Functions/ToCharTest.php | 99 ++++++++++++++++++- .../ORM/Query/AST/Functions/ToDateTest.php | 34 ++++++- .../ORM/Query/AST/Functions/ToNumberTest.php | 27 ++++- .../Query/AST/Functions/ToTimestampTest.php | 36 ++++++- .../ORM/Query/AST/Functions/ToCharTest.php | 11 +++ .../ORM/Query/AST/Functions/ToDateTest.php | 9 ++ .../ORM/Query/AST/Functions/ToNumberTest.php | 9 ++ .../Query/AST/Functions/ToTimestampTest.php | 9 ++ 11 files changed, 233 insertions(+), 13 deletions(-) diff --git a/fixtures/MartinGeorgiev/Doctrine/Entity/ContainsDates.php b/fixtures/MartinGeorgiev/Doctrine/Entity/ContainsDates.php index 6c6e25d5..d5e33a74 100644 --- a/fixtures/MartinGeorgiev/Doctrine/Entity/ContainsDates.php +++ b/fixtures/MartinGeorgiev/Doctrine/Entity/ContainsDates.php @@ -27,4 +27,7 @@ class ContainsDates extends Entity #[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE)] public \DateTimeImmutable $datetimetz2; + + #[ORM\Column(type: Types::DATEINTERVAL)] + public \DateTimeImmutable $dateinterval1; } diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToChar.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToChar.php index aa697eea..dbf816c9 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToChar.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToChar.php @@ -14,7 +14,7 @@ class ToChar extends BaseFunction protected function customizeFunction(): void { $this->setFunctionPrototype('to_char(%s, %s)'); - $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('ArithmeticFactor'); $this->addNodeMapping('StringPrimary'); } } diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateTestCase.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateTestCase.php index b97e46ec..d4930f4d 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateTestCase.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateTestCase.php @@ -33,7 +33,8 @@ protected function createTestTableForDateFixture(): void time1 TIME, time2 TIME, datetimetz1 TIMESTAMPTZ, - datetimetz2 TIMESTAMPTZ + datetimetz2 TIMESTAMPTZ, + dateinterval1 INTERVAL ) ', $fullTableName); @@ -43,8 +44,8 @@ protected function createTestTableForDateFixture(): void protected function insertTestDataForDateFixture(): void { $sql = \sprintf(' - INSERT INTO %s.containsdates (date1, date2, datetime1, datetime2, time1, time2, datetimetz1, datetimetz2) VALUES - (\'2023-06-15\', \'2023-06-16\', \'2023-06-15 10:30:00\', \'2023-06-16 11:45:00\', \'10:30:00\', \'11:45:00\', \'2023-06-15 10:30:00+00\', \'2023-06-16 11:45:00+00\') + INSERT INTO %s.containsdates (date1, date2, datetime1, datetime2, time1, time2, datetimetz1, datetimetz2, dateinterval1) VALUES + (\'2023-06-15\', \'2023-06-16\', \'2023-06-15 10:30:00\', \'2023-06-16 11:45:00\', \'10:30:00\', \'11:45:00\', \'2023-06-15 10:30:00+00\', \'2023-06-16 11:45:00+00\', \'15h 2m 12s\') ', self::DATABASE_SCHEMA); $this->connection->executeStatement($sql); } diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php index b2e5de19..86761c8b 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php @@ -4,21 +4,116 @@ namespace Tests\Integration\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; +use Doctrine\DBAL\Exception; +use Doctrine\ORM\Query\QueryException; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToChar; +use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTimestamp; class ToCharTest extends DateTestCase { + protected function setUp(): void + { + parent::setUp(); + $this->createSimpleNumericTableWithDateFixture(); + } + protected function getStringFunctions(): array { return [ 'to_char' => ToChar::class, + 'to_timestamp' => ToTimestamp::class, ]; } - public function test_tochar(): void + public function test_tochar_for_timestamp(): void { - $dql = "SELECT to_char(t.datetimetz1, 'HH12:MI:SS') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsDates t WHERE t.id = 1"; + $dql = "SELECT to_char(t.datetimetz1, 'HH12:MI:SS') AS result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsDates t WHERE t.id = 1"; $result = $this->executeDqlQuery($dql); static::assertSame('10:30:00', $result[0]['result']); } + + public function test_tochar_for_interval(): void + { + $dql = "SELECT to_char(t.dateinterval1, 'HH24:MI:SS') AS result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsDates t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + static::assertSame('15:02:12', $result[0]['result']); + } + + public function test_tochar_for_numeric(): void + { + $dql = "SELECT to_char(t.decimal1, '999D99S') AS result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsNumerics t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + static::assertSame('125.80-', $result[0]['result']); + } + + public function test_tochar_for_numeric_literal(): void + { + $dql = "SELECT to_char(125.80, '999D99S') AS result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsNumerics t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + static::assertSame('125.80+', $result[0]['result']); + } + + public function test_tochar_for_numeric_literal_negative(): void + { + $dql = "SELECT to_char(125.80, '999D99S') AS result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsNumerics t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + static::assertSame('125.80+', $result[0]['result']); + } + + public function test_tochar_with_timestamp_function(): void + { + $dql = "SELECT to_char(to_timestamp('05 Dec 2000 at 11:55 and 32 seconds', 'DD Mon YYYY tt HH24:MI ttt SS ttttttt'), 'HH24:MI:SS') AS result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsDates t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + static::assertSame('11:55:32', $result[0]['result']); + } + + public function test_todate_with_invalid_input(): void + { + $this->expectException(QueryException::class); + $dql = "SELECT to_date('invalid_date', 'DD Mon YYYY') AS result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; + $this->executeDqlQuery($dql); + } + + public function test_todate_with_invalid_format(): void + { + $this->expectException(Exception::class); + $dql = "SELECT to_char(t.decimal1, 'invalid_format') FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsNumerics t WHERE t.id = 1"; + $this->executeDqlQuery($dql); + } + + public function test_tochar_invalid_input(): void + { + $this->expectException(QueryException::class); + $dql = "SELECT to_char(NULL, '999D99S') AS result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsNumerics t WHERE t.id = 1"; + $this->executeDqlQuery($dql); + } + + protected function createSimpleNumericTableWithDateFixture(): void + { + $tableName = 'containsnumerics'; + + $this->dropTestTableIfItExists($tableName); + + $fullTableName = \sprintf('%s.%s', self::DATABASE_SCHEMA, $tableName); + $sql = \sprintf( + ' + CREATE TABLE %s ( + id serial PRIMARY KEY, + decimal1 DECIMAL + ) + ', + $fullTableName + ); + + $this->connection->executeStatement($sql); + + $sql = \sprintf( + ' + INSERT INTO %s.containsnumerics (decimal1) VALUES + (-125.8) + ', + self::DATABASE_SCHEMA + ); + $this->connection->executeStatement($sql); + } } diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php index 9c05fb18..40d37b4e 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php @@ -4,9 +4,11 @@ namespace Tests\Integration\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; +use Doctrine\DBAL\Exception\DriverException; +use Doctrine\ORM\Query\QueryException; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToDate; -class ToDateTest extends JsonTestCase +class ToDateTest extends TextTestCase { protected function getStringFunctions(): array { @@ -17,8 +19,36 @@ protected function getStringFunctions(): array public function test_todate(): void { - $dql = "SELECT to_date('05 Dec 2000', 'DD Mon YYYY') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; + $dql = "SELECT to_date('05 Dec 2000', 'DD Mon YYYY') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; $result = $this->executeDqlQuery($dql); static::assertSame('2000-12-05', $result[0]['result']); } + + public function test_todate_with_invalid_input(): void + { + $this->expectException(DriverException::class); + $dql = "SELECT to_date('invalid_date', 'DD Mon YYYY') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; + $this->executeDqlQuery($dql); + } + + public function test_todate_with_invalid_format(): void + { + $dql = "SELECT to_date('05 Dec 2000', 'invalid_format') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + static::assertSame('2005-01-01', $result[0]['result']); + } + + public function test_todate_with_wrong_type_format(): void + { + $this->expectException(QueryException::class); + $dql = "SELECT to_date('05 Dec 2000', 1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; + $this->executeDqlQuery($dql); + } + + public function test_todate_with_wrong_type_input(): void + { + $this->expectException(QueryException::class); + $dql = "SELECT to_date(null, 'DD Mon YYYY') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; + $this->executeDqlQuery($dql); + } } diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php index 1f1d3ef6..0814b998 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php @@ -4,9 +4,11 @@ namespace Tests\Integration\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; +use Doctrine\DBAL\Exception; +use Doctrine\ORM\Query\QueryException; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToNumber; -class ToNumberTest extends JsonTestCase +class ToNumberTest extends TextTestCase { protected function getStringFunctions(): array { @@ -17,8 +19,29 @@ protected function getStringFunctions(): array public function test_tonumber(): void { - $dql = "SELECT to_number('12,454.8-', '99G999D9S') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; + $dql = "SELECT to_number('12,454.8-', '99G999D9S') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; $result = $this->executeDqlQuery($dql); static::assertSame('-12454.8', $result[0]['result']); } + + public function test_tonumber_with_invalid_format(): void + { + $this->expectException(Exception::class); + $dql = "SELECT to_number('12,454.8-', 'invalid_format') FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; + $this->executeDqlQuery($dql); + } + + public function test_tonumber_with_wrong_type_format(): void + { + $this->expectException(QueryException::class); + $dql = "SELECT to_number('12,454.8-', null) FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; + $this->executeDqlQuery($dql); + } + + public function test_tonumber_with_wrong_type_input(): void + { + $this->expectException(QueryException::class); + $dql = "SELECT to_number(123456, '999D99S') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; + $this->executeDqlQuery($dql); + } } diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php index 27f6cc78..cce04113 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php @@ -4,9 +4,11 @@ namespace Tests\Integration\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; +use Doctrine\DBAL\Exception\DriverException; +use Doctrine\ORM\Query\QueryException; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTimestamp; -class ToTimestampTest extends JsonTestCase +class ToTimestampTest extends TextTestCase { protected function getStringFunctions(): array { @@ -17,8 +19,36 @@ protected function getStringFunctions(): array public function test_totimestamp(): void { - $dql = "SELECT to_timestamp('05 Dec 2000', 'DD Mon YYYY') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; + $dql = "SELECT to_timestamp('05 Dec 2000 at 11:55 and 32 seconds', 'DD Mon YYYY tt HH24:MI ttt SS ttttttt') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; $result = $this->executeDqlQuery($dql); - static::assertSame('2000-12-05 00:00:00+00', $result[0]['result']); + static::assertSame('2000-12-05 11:55:32+00', $result[0]['result']); + } + + public function test_totimestamp_with_invalid_input(): void + { + $this->expectException(DriverException::class); + $dql = "SELECT to_timestamp('invalid_date', 'DD Mon YYYY') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; + $this->executeDqlQuery($dql); + } + + public function test_totimestamp_with_invalid_format(): void + { + $dql = "SELECT to_timestamp('05 Dec 2000', 'invalid_format') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + static::assertSame('2005-01-01 00:00:00+00', $result[0]['result']); + } + + public function test_totimestamp_with_wrong_type_format(): void + { + $this->expectException(QueryException::class); + $dql = "SELECT to_timestamp('05 Dec 2000', 1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; + $this->executeDqlQuery($dql); + } + + public function test_totimestamp_with_wrong_type_input(): void + { + $this->expectException(QueryException::class); + $dql = "SELECT to_timestamp(null, 'DD Mon YYYY') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; + $this->executeDqlQuery($dql); } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php index 0e708103..935f0c52 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php @@ -4,6 +4,7 @@ namespace Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; +use Doctrine\ORM\Query\QueryException; use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsDates; use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsDecimals; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToChar; @@ -21,6 +22,7 @@ protected function getExpectedSqlStatements(): array { return [ "SELECT to_char(c0_.datetime1, 'HH12:MI:SS') AS sclr_0 FROM ContainsDates c0_", + "SELECT to_char(c0_.dateinterval1, 'HH24:MI:SS') AS sclr_0 FROM ContainsDates c0_", "SELECT to_char(c0_.decimal1, '999D99S') AS sclr_0 FROM ContainsDecimals c0_", ]; } @@ -29,7 +31,16 @@ protected function getDqlStatements(): array { return [ \sprintf("SELECT TO_CHAR(e.datetime1, 'HH12:MI:SS') FROM %s e", ContainsDates::class), + \sprintf("SELECT TO_CHAR(e.dateinterval1, 'HH24:MI:SS') FROM %s e", ContainsDates::class), \sprintf("SELECT TO_CHAR(e.decimal1, '999D99S') FROM %s e", ContainsDecimals::class), ]; } + + public function test_missing_format_throws_exception(): void + { + $this->expectException(QueryException::class); + + $dql = \sprintf('SELECT TO_CHAR(e.decimal1) FROM %s e', ContainsDecimals::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php index 8ac71687..69af8f09 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php @@ -4,6 +4,7 @@ namespace Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; +use Doctrine\ORM\Query\QueryException; use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsTexts; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToDate; @@ -29,4 +30,12 @@ protected function getDqlStatements(): array \sprintf("SELECT TO_DATE(e.text1, 'DD Mon YYYY') FROM %s e", ContainsTexts::class), ]; } + + public function test_missing_format_throws_exception(): void + { + $this->expectException(QueryException::class); + + $dql = \sprintf('SELECT TO_DATE(e.text1) FROM %s e', ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php index 74c03266..a40757f0 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php @@ -4,6 +4,7 @@ namespace Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; +use Doctrine\ORM\Query\QueryException; use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsTexts; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToNumber; @@ -29,4 +30,12 @@ protected function getDqlStatements(): array \sprintf("SELECT TO_NUMBER(e.text1, '99G999D9S') FROM %s e", ContainsTexts::class), ]; } + + public function test_missing_format_throws_exception(): void + { + $this->expectException(QueryException::class); + + $dql = \sprintf('SELECT TO_NUMBER(e.text1) FROM %s e', ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php index 95393989..485d955b 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php @@ -4,6 +4,7 @@ namespace Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; +use Doctrine\ORM\Query\QueryException; use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsTexts; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTimestamp; @@ -29,4 +30,12 @@ protected function getDqlStatements(): array \sprintf("SELECT TO_TIMESTAMP(e.text1, 'DD Mon YYYY') FROM %s e", ContainsTexts::class), ]; } + + public function test_missing_format_throws_exception(): void + { + $this->expectException(QueryException::class); + + $dql = \sprintf('SELECT TO_TIMESTAMP(e.text1) FROM %s e', ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } } From 800a8e65ba66c27d8ad5c885706e88cf2ccd1de0 Mon Sep 17 00:00:00 2001 From: Andrei Karpilin Date: Thu, 29 May 2025 09:59:42 +0100 Subject: [PATCH 3/5] Updates documentation URL and adds since tag Updates the documentation URL to reflect the latest PostgreSQL documentation (version 17). Adds a `@since` tag to indicate the version in which these functions were introduced. Minor corrections to tests --- .../ORM/Query/AST/Functions/ToChar.php | 3 +- .../ORM/Query/AST/Functions/ToDate.php | 3 +- .../ORM/Query/AST/Functions/ToNumber.php | 3 +- .../ORM/Query/AST/Functions/ToTimestamp.php | 3 +- .../ORM/Query/AST/Functions/ToCharTest.php | 79 +++++++++++++------ .../ORM/Query/AST/Functions/ToDateTest.php | 6 +- .../ORM/Query/AST/Functions/ToNumberTest.php | 6 +- .../Query/AST/Functions/ToTimestampTest.php | 6 +- .../ORM/Query/AST/Functions/ToCharTest.php | 4 +- .../ORM/Query/AST/Functions/ToDateTest.php | 4 +- .../ORM/Query/AST/Functions/ToNumberTest.php | 4 +- .../Query/AST/Functions/ToTimestampTest.php | 4 +- 12 files changed, 85 insertions(+), 40 deletions(-) diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToChar.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToChar.php index dbf816c9..56ece8fd 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToChar.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToChar.php @@ -7,7 +7,8 @@ /** * Implementation of PostgreSQL to_char(). * - * @see https://www.postgresql.org/docs/current/functions-formatting.html + * @see https://www.postgresql.org/docs/17/functions-formatting.html + * @since 3.3.0 */ class ToChar extends BaseFunction { diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDate.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDate.php index 02cf2cbb..177bd0e5 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDate.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDate.php @@ -7,7 +7,8 @@ /** * Implementation of PostgreSQL to_date(). * - * @see https://www.postgresql.org/docs/current/functions-formatting.html + * @see https://www.postgresql.org/docs/17/functions-formatting.html + * @since 3.3.0 */ class ToDate extends BaseFunction { diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumber.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumber.php index 8bc59911..d528c819 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumber.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumber.php @@ -7,7 +7,8 @@ /** * Implementation of PostgreSQL to_number(). * - * @see https://www.postgresql.org/docs/current/functions-formatting.html + * @see https://www.postgresql.org/docs/17/functions-formatting.html + * @since 3.3.0 */ class ToNumber extends BaseFunction { diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestamp.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestamp.php index 0343f9c4..40a00dbb 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestamp.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestamp.php @@ -7,7 +7,8 @@ /** * Implementation of PostgreSQL to_timestamp(). * - * @see https://www.postgresql.org/docs/current/functions-formatting.html + * @see https://www.postgresql.org/docs/17/functions-formatting.html + * @since 3.3.0 */ class ToTimestamp extends BaseFunction { diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php index 86761c8b..231b989b 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php @@ -8,13 +8,15 @@ use Doctrine\ORM\Query\QueryException; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToChar; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTimestamp; +use Tests\Integration\MartinGeorgiev\TestCase; -class ToCharTest extends DateTestCase +class ToCharTest extends TestCase { protected function setUp(): void { parent::setUp(); - $this->createSimpleNumericTableWithDateFixture(); + $this->createContainsDatesTableWithFixture(); + $this->createContainsNumericsTableWithFixture(); } protected function getStringFunctions(): array @@ -55,65 +57,96 @@ public function test_tochar_for_numeric_literal(): void public function test_tochar_for_numeric_literal_negative(): void { - $dql = "SELECT to_char(125.80, '999D99S') AS result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsNumerics t WHERE t.id = 1"; + $dql = "SELECT to_char(-125.80, '999D99S') AS result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsNumerics t WHERE t.id = 1"; $result = $this->executeDqlQuery($dql); - static::assertSame('125.80+', $result[0]['result']); + static::assertSame('125.80-', $result[0]['result']); } - public function test_tochar_with_timestamp_function(): void + public function test_tochar_with_subfunction(): void { $dql = "SELECT to_char(to_timestamp('05 Dec 2000 at 11:55 and 32 seconds', 'DD Mon YYYY tt HH24:MI ttt SS ttttttt'), 'HH24:MI:SS') AS result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsDates t WHERE t.id = 1"; $result = $this->executeDqlQuery($dql); static::assertSame('11:55:32', $result[0]['result']); } - public function test_todate_with_invalid_input(): void + public function test_todate_throws_with_invalid_input(): void { $this->expectException(QueryException::class); $dql = "SELECT to_date('invalid_date', 'DD Mon YYYY') AS result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; $this->executeDqlQuery($dql); } - public function test_todate_with_invalid_format(): void + public function test_todate_throws_with_invalid_format(): void { $this->expectException(Exception::class); $dql = "SELECT to_char(t.decimal1, 'invalid_format') FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsNumerics t WHERE t.id = 1"; $this->executeDqlQuery($dql); } - public function test_tochar_invalid_input(): void + public function test_tochar_throws_with_unsupported_null_input(): void { $this->expectException(QueryException::class); $dql = "SELECT to_char(NULL, '999D99S') AS result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsNumerics t WHERE t.id = 1"; $this->executeDqlQuery($dql); } - protected function createSimpleNumericTableWithDateFixture(): void + private function createContainsDatesTableWithFixture(): void + { + $tableName = 'containsdates'; + + $this->createTestSchema(); + $this->dropTestTableIfItExists($tableName); + + $fullTableName = \sprintf('%s.%s', self::DATABASE_SCHEMA, $tableName); + $sql = \sprintf(' + CREATE TABLE %s ( + id SERIAL PRIMARY KEY, + date1 DATE, + date2 DATE, + datetime1 TIMESTAMP, + datetime2 TIMESTAMP, + time1 TIME, + time2 TIME, + datetimetz1 TIMESTAMPTZ, + datetimetz2 TIMESTAMPTZ, + dateinterval1 INTERVAL + ) + ', $fullTableName); + + $this->connection->executeStatement($sql); + + $sql = \sprintf(' + INSERT INTO %s.containsdates (date1, date2, datetime1, datetime2, time1, time2, datetimetz1, datetimetz2, dateinterval1) VALUES + (\'2023-06-15\', \'2023-06-16\', \'2023-06-15 10:30:00\', \'2023-06-16 11:45:00\', \'10:30:00\', \'11:45:00\', \'2023-06-15 10:30:00+00\', \'2023-06-16 11:45:00+00\', \'15h 2m 12s\') + ', self::DATABASE_SCHEMA); + $this->connection->executeStatement($sql); + } + + private function createContainsNumericsTableWithFixture(): void { $tableName = 'containsnumerics'; $this->dropTestTableIfItExists($tableName); $fullTableName = \sprintf('%s.%s', self::DATABASE_SCHEMA, $tableName); - $sql = \sprintf( - ' + $sql = \sprintf(' CREATE TABLE %s ( - id serial PRIMARY KEY, - decimal1 DECIMAL + id SERIAL PRIMARY KEY, + integer1 INTEGER, + integer2 INTEGER, + bigint1 BIGINT, + bigint2 BIGINT, + decimal1 DECIMAL, + decimal2 DECIMAL ) - ', - $fullTableName - ); + ', $fullTableName); $this->connection->executeStatement($sql); - $sql = \sprintf( - ' - INSERT INTO %s.containsnumerics (decimal1) VALUES - (-125.8) - ', - self::DATABASE_SCHEMA - ); + $sql = \sprintf(' + INSERT INTO %s.containsnumerics (integer1, integer2, bigint1, bigint2, decimal1, decimal2) VALUES + (10, 20, 1000, 2000, -125.8, 20.5) + ', self::DATABASE_SCHEMA); $this->connection->executeStatement($sql); } } diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php index 40d37b4e..620d235c 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php @@ -24,7 +24,7 @@ public function test_todate(): void static::assertSame('2000-12-05', $result[0]['result']); } - public function test_todate_with_invalid_input(): void + public function test_todate_throws_with_invalid_input(): void { $this->expectException(DriverException::class); $dql = "SELECT to_date('invalid_date', 'DD Mon YYYY') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; @@ -38,14 +38,14 @@ public function test_todate_with_invalid_format(): void static::assertSame('2005-01-01', $result[0]['result']); } - public function test_todate_with_wrong_type_format(): void + public function test_todate_throws_with_unsupported_format_type(): void { $this->expectException(QueryException::class); $dql = "SELECT to_date('05 Dec 2000', 1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; $this->executeDqlQuery($dql); } - public function test_todate_with_wrong_type_input(): void + public function test_todate_throws_with_unsupported_null_input(): void { $this->expectException(QueryException::class); $dql = "SELECT to_date(null, 'DD Mon YYYY') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php index 0814b998..f6d883ac 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php @@ -24,21 +24,21 @@ public function test_tonumber(): void static::assertSame('-12454.8', $result[0]['result']); } - public function test_tonumber_with_invalid_format(): void + public function test_tonumber_throws_with_invalid_format(): void { $this->expectException(Exception::class); $dql = "SELECT to_number('12,454.8-', 'invalid_format') FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; $this->executeDqlQuery($dql); } - public function test_tonumber_with_wrong_type_format(): void + public function test_tonumber_throws_with_unsupported_null_format(): void { $this->expectException(QueryException::class); $dql = "SELECT to_number('12,454.8-', null) FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; $this->executeDqlQuery($dql); } - public function test_tonumber_with_wrong_type_input(): void + public function test_tonumber_throws_with_unsupported_input_type(): void { $this->expectException(QueryException::class); $dql = "SELECT to_number(123456, '999D99S') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php index cce04113..742ab225 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php @@ -24,7 +24,7 @@ public function test_totimestamp(): void static::assertSame('2000-12-05 11:55:32+00', $result[0]['result']); } - public function test_totimestamp_with_invalid_input(): void + public function test_totimestamp_throws_with_invalid_input(): void { $this->expectException(DriverException::class); $dql = "SELECT to_timestamp('invalid_date', 'DD Mon YYYY') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; @@ -38,14 +38,14 @@ public function test_totimestamp_with_invalid_format(): void static::assertSame('2005-01-01 00:00:00+00', $result[0]['result']); } - public function test_totimestamp_with_wrong_type_format(): void + public function test_totimestamp_throws_with_unsupported_format_type(): void { $this->expectException(QueryException::class); $dql = "SELECT to_timestamp('05 Dec 2000', 1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; $this->executeDqlQuery($dql); } - public function test_totimestamp_with_wrong_type_input(): void + public function test_totimestamp_throws_with_unsupported_null_input(): void { $this->expectException(QueryException::class); $dql = "SELECT to_timestamp(null, 'DD Mon YYYY') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php index 935f0c52..603861e2 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php @@ -8,6 +8,7 @@ use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsDates; use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsDecimals; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToChar; +use PHPUnit\Framework\Attributes\Test; class ToCharTest extends TestCase { @@ -36,7 +37,8 @@ protected function getDqlStatements(): array ]; } - public function test_missing_format_throws_exception(): void + #[Test] + public function throws_exception_when_argument_missing(): void { $this->expectException(QueryException::class); diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php index 69af8f09..c32f7b2d 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php @@ -7,6 +7,7 @@ use Doctrine\ORM\Query\QueryException; use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsTexts; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToDate; +use PHPUnit\Framework\Attributes\Test; class ToDateTest extends TestCase { @@ -31,7 +32,8 @@ protected function getDqlStatements(): array ]; } - public function test_missing_format_throws_exception(): void + #[Test] + public function throws_exception_when_argument_missing(): void { $this->expectException(QueryException::class); diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php index a40757f0..400ff890 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php @@ -7,6 +7,7 @@ use Doctrine\ORM\Query\QueryException; use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsTexts; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToNumber; +use PHPUnit\Framework\Attributes\Test; class ToNumberTest extends TestCase { @@ -31,7 +32,8 @@ protected function getDqlStatements(): array ]; } - public function test_missing_format_throws_exception(): void + #[Test] + public function throws_exception_when_argument_missing(): void { $this->expectException(QueryException::class); diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php index 485d955b..ff0983d4 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php @@ -7,6 +7,7 @@ use Doctrine\ORM\Query\QueryException; use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsTexts; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTimestamp; +use PHPUnit\Framework\Attributes\Test; class ToTimestampTest extends TestCase { @@ -31,7 +32,8 @@ protected function getDqlStatements(): array ]; } - public function test_missing_format_throws_exception(): void + #[Test] + public function throws_exception_when_argument_missing(): void { $this->expectException(QueryException::class); From 066acdaefea1f21d3fac23ba32c456fb28d148fa Mon Sep 17 00:00:00 2001 From: Andrei Karpilin Date: Thu, 29 May 2025 10:21:45 +0100 Subject: [PATCH 4/5] further corrections to tests --- .../Doctrine/ORM/Query/AST/Functions/ToCharTest.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php index 231b989b..703d62ff 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php @@ -5,6 +5,7 @@ namespace Tests\Integration\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Exception\DriverException; use Doctrine\ORM\Query\QueryException; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToChar; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTimestamp; @@ -15,6 +16,7 @@ class ToCharTest extends TestCase protected function setUp(): void { parent::setUp(); + $this->createTestSchema(); $this->createContainsDatesTableWithFixture(); $this->createContainsNumericsTableWithFixture(); } @@ -69,14 +71,14 @@ public function test_tochar_with_subfunction(): void static::assertSame('11:55:32', $result[0]['result']); } - public function test_todate_throws_with_invalid_input(): void + public function test_tochar_throws_with_invalid_input_type(): void { - $this->expectException(QueryException::class); - $dql = "SELECT to_date('invalid_date', 'DD Mon YYYY') AS result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; + $this->expectException(DriverException::class); + $dql = "SELECT to_char('can only be timestamp, interval or numeric, never a string', 'DD Mon YYYY') AS result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsDates t WHERE t.id = 1"; $this->executeDqlQuery($dql); } - public function test_todate_throws_with_invalid_format(): void + public function test_tochar_throws_with_invalid_format(): void { $this->expectException(Exception::class); $dql = "SELECT to_char(t.decimal1, 'invalid_format') FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsNumerics t WHERE t.id = 1"; @@ -94,7 +96,6 @@ private function createContainsDatesTableWithFixture(): void { $tableName = 'containsdates'; - $this->createTestSchema(); $this->dropTestTableIfItExists($tableName); $fullTableName = \sprintf('%s.%s', self::DATABASE_SCHEMA, $tableName); From 93b5f8badd24a4320a88276ab123ec9ec932655c Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Sun, 15 Jun 2025 20:18:54 +0100 Subject: [PATCH 5/5] Apply suggestions from code review --- .../Doctrine/ORM/Query/AST/Functions/ToCharTest.php | 8 ++++---- .../Doctrine/ORM/Query/AST/Functions/ToCharTest.php | 2 +- .../Doctrine/ORM/Query/AST/Functions/ToDateTest.php | 2 +- .../Doctrine/ORM/Query/AST/Functions/ToNumberTest.php | 2 +- .../Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php index 703d62ff..5e52d198 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php @@ -17,8 +17,8 @@ protected function setUp(): void { parent::setUp(); $this->createTestSchema(); - $this->createContainsDatesTableWithFixture(); - $this->createContainsNumericsTableWithFixture(); + $this->createTestTableForDateFixture(); + $this->createTestTableForNumericFixture(); } protected function getStringFunctions(): array @@ -92,7 +92,7 @@ public function test_tochar_throws_with_unsupported_null_input(): void $this->executeDqlQuery($dql); } - private function createContainsDatesTableWithFixture(): void + private function createTestTableForDateFixture(): void { $tableName = 'containsdates'; @@ -123,7 +123,7 @@ private function createContainsDatesTableWithFixture(): void $this->connection->executeStatement($sql); } - private function createContainsNumericsTableWithFixture(): void + private function createTestTableForNumericFixture(): void { $tableName = 'containsnumerics'; diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php index 603861e2..4d7b8870 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php @@ -38,7 +38,7 @@ protected function getDqlStatements(): array } #[Test] - public function throws_exception_when_argument_missing(): void + public function throws_exception_when_argument_is_missing(): void { $this->expectException(QueryException::class); diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php index c32f7b2d..ed837d2f 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php @@ -33,7 +33,7 @@ protected function getDqlStatements(): array } #[Test] - public function throws_exception_when_argument_missing(): void + public function throws_exception_when_argument_is_missing(): void { $this->expectException(QueryException::class); diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php index 400ff890..c2e106d1 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php @@ -33,7 +33,7 @@ protected function getDqlStatements(): array } #[Test] - public function throws_exception_when_argument_missing(): void + public function throws_exception_when_argument_is_missing(): void { $this->expectException(QueryException::class); diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php index ff0983d4..5b54d880 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php @@ -33,7 +33,7 @@ protected function getDqlStatements(): array } #[Test] - public function throws_exception_when_argument_missing(): void + public function throws_exception_when_argument_is_missing(): void { $this->expectException(QueryException::class);