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/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 new file mode 100644 index 00000000..56ece8fd --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToChar.php @@ -0,0 +1,21 @@ +setFunctionPrototype('to_char(%s, %s)'); + $this->addNodeMapping('ArithmeticFactor'); + $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..177bd0e5 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDate.php @@ -0,0 +1,21 @@ +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..d528c819 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumber.php @@ -0,0 +1,21 @@ +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..40a00dbb --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestamp.php @@ -0,0 +1,21 @@ +setFunctionPrototype('to_timestamp(%s, %s)'); + $this->addNodeMapping('StringPrimary'); + $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 new file mode 100644 index 00000000..5e52d198 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php @@ -0,0 +1,153 @@ +createTestSchema(); + $this->createTestTableForDateFixture(); + $this->createTestTableForNumericFixture(); + } + + protected function getStringFunctions(): array + { + return [ + 'to_char' => ToChar::class, + 'to_timestamp' => ToTimestamp::class, + ]; + } + + 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"; + $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_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_tochar_throws_with_invalid_input_type(): void + { + $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_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"; + $this->executeDqlQuery($dql); + } + + 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); + } + + private function createTestTableForDateFixture(): void + { + $tableName = 'containsdates'; + + $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 createTestTableForNumericFixture(): void + { + $tableName = 'containsnumerics'; + + $this->dropTestTableIfItExists($tableName); + + $fullTableName = \sprintf('%s.%s', self::DATABASE_SCHEMA, $tableName); + $sql = \sprintf(' + CREATE TABLE %s ( + id SERIAL PRIMARY KEY, + integer1 INTEGER, + integer2 INTEGER, + bigint1 BIGINT, + bigint2 BIGINT, + decimal1 DECIMAL, + decimal2 DECIMAL + ) + ', $fullTableName); + + $this->connection->executeStatement($sql); + + $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 new file mode 100644 index 00000000..620d235c --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php @@ -0,0 +1,54 @@ + ToDate::class, + ]; + } + + public function test_todate(): void + { + $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_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"; + $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_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_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"; + $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 new file mode 100644 index 00000000..f6d883ac --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php @@ -0,0 +1,47 @@ + ToNumber::class, + ]; + } + + public function test_tonumber(): void + { + $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_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_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_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"; + $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 new file mode 100644 index 00000000..742ab225 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php @@ -0,0 +1,54 @@ + ToTimestamp::class, + ]; + } + + public function test_totimestamp(): void + { + $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 11:55:32+00', $result[0]['result']); + } + + 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"; + $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_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_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"; + $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 new file mode 100644 index 00000000..4d7b8870 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToCharTest.php @@ -0,0 +1,48 @@ + 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_.dateinterval1, 'HH24: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.dateinterval1, 'HH24:MI:SS') FROM %s e", ContainsDates::class), + \sprintf("SELECT TO_CHAR(e.decimal1, '999D99S') FROM %s e", ContainsDecimals::class), + ]; + } + + #[Test] + public function throws_exception_when_argument_is_missing(): 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 new file mode 100644 index 00000000..ed837d2f --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToDateTest.php @@ -0,0 +1,43 @@ + 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), + ]; + } + + #[Test] + public function throws_exception_when_argument_is_missing(): 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 new file mode 100644 index 00000000..c2e106d1 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php @@ -0,0 +1,43 @@ + 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), + ]; + } + + #[Test] + public function throws_exception_when_argument_is_missing(): 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 new file mode 100644 index 00000000..5b54d880 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToTimestampTest.php @@ -0,0 +1,43 @@ + 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), + ]; + } + + #[Test] + public function throws_exception_when_argument_is_missing(): void + { + $this->expectException(QueryException::class); + + $dql = \sprintf('SELECT TO_TIMESTAMP(e.text1) FROM %s e', ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } +}