From dc49c80c73ef5cad5a8fe6d0b1a74dfc71fe57d7 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Tue, 21 Oct 2025 19:46:17 +0100 Subject: [PATCH 01/12] ci: optimize CI workflow performance --- .github/workflows/ci.yml | 4 ++++ .github/workflows/integration-tests.yml | 11 ++++++++--- .github/workflows/unit-tests.yml | 11 ++++++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7277a940..bbf4965c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,10 @@ on: branches: - main +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + permissions: contents: read diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index bd597d77..4aa8a00a 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -10,6 +10,10 @@ on: branches: - main +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + permissions: contents: read @@ -81,7 +85,7 @@ jobs: uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2 with: php-version: ${{ matrix.php }} - coverage: xdebug + coverage: ${{ matrix.calculate-code-coverage && 'xdebug' || 'none' }} extensions: ctype, json, mbstring, pdo_pgsql tools: composer @@ -93,9 +97,10 @@ jobs: uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + key: ${{ runner.os }}-php-${{ matrix.php }}-pg-${{ matrix.postgres }}-${{ hashFiles('**/composer.lock') }} restore-keys: | - ${{ runner.os }}-php- + ${{ runner.os }}-php-${{ matrix.php }}-pg-${{ matrix.postgres }}- + ${{ runner.os }}-php-${{ matrix.php }}- - name: Install dependencies run: composer install --prefer-dist --no-interaction --no-progress diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index e8645328..373bda03 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -10,6 +10,10 @@ on: branches: - main +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + permissions: contents: read @@ -63,7 +67,7 @@ jobs: uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2 with: php-version: ${{ matrix.php }} - coverage: xdebug + coverage: ${{ matrix.calculate-code-coverage && 'xdebug' || 'none' }} extensions: ctype, json, mbstring tools: composer @@ -72,9 +76,10 @@ jobs: uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + key: ${{ runner.os }}-php-${{ matrix.php }}-orm-${{ matrix.doctrine-orm }}-lexer-${{ matrix.doctrine-lexer }}-${{ hashFiles('**/composer.lock') }} restore-keys: | - ${{ runner.os }}-php- + ${{ runner.os }}-php-${{ matrix.php }}-orm-${{ matrix.doctrine-orm }}-lexer-${{ matrix.doctrine-lexer }}- + ${{ runner.os }}-php-${{ matrix.php }}- - name: Install Doctrine Lexer dependency run: | From 66002c55d4e5e09ce8d65dd2d8e3f68f53f6c289 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Tue, 21 Oct 2025 19:50:07 +0100 Subject: [PATCH 02/12] no message --- .github/workflows/integration-tests.yml | 2 +- .github/workflows/unit-tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 4aa8a00a..0162e502 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -85,7 +85,7 @@ jobs: uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2 with: php-version: ${{ matrix.php }} - coverage: ${{ matrix.calculate-code-coverage && 'xdebug' || 'none' }} + coverage: ${{ matrix.calculate-code-coverage == true && 'xdebug' || 'none' }} extensions: ctype, json, mbstring, pdo_pgsql tools: composer diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 373bda03..81a6aca9 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -67,7 +67,7 @@ jobs: uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2 with: php-version: ${{ matrix.php }} - coverage: ${{ matrix.calculate-code-coverage && 'xdebug' || 'none' }} + coverage: ${{ matrix.calculate-code-coverage == true && 'xdebug' || 'none' }} extensions: ctype, json, mbstring tools: composer From 238fda017e6460015bd02fd9aadc883cda37063d Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Tue, 21 Oct 2025 19:56:58 +0100 Subject: [PATCH 03/12] no message --- .github/workflows/integration-tests.yml | 1 + .github/workflows/unit-tests.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 0162e502..e7e4d782 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -137,6 +137,7 @@ jobs: POSTGRES_DB: postgres_doctrine_test POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres + XDEBUG_MODE: ${{ matrix.calculate-code-coverage == true && 'coverage' || 'off' }} - name: Upload coverage results to Coveralls if: matrix.calculate-code-coverage == true diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 81a6aca9..5433420c 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -111,6 +111,8 @@ jobs: - name: Run unit test suite run: composer run-unit-tests + env: + XDEBUG_MODE: ${{ matrix.calculate-code-coverage == true && 'coverage' || 'off' }} - name: Upload coverage results to Coveralls if: matrix.calculate-code-coverage == true From e4c0ea343ca4ccf6737d73b492f28a8f01dea9eb Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Tue, 21 Oct 2025 20:00:33 +0100 Subject: [PATCH 04/12] no message --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3164976b..160991e4 100644 --- a/composer.json +++ b/composer.json @@ -72,7 +72,7 @@ "phpstan analyse --configuration=./ci/phpstan/config.neon" ], "phpunit": [ - "XDEBUG_MODE=coverage phpunit" + "XDEBUG_MODE=${XDEBUG_MODE:-coverage} phpunit" ], "phpunit:unit": [ "@phpunit --configuration=./ci/phpunit/config-unit.xml" From 22f9ebe0cf990ab3e139b66246352411bae2f8ce Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Tue, 21 Oct 2025 20:05:52 +0100 Subject: [PATCH 05/12] no message --- composer.json | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 160991e4..9dade7c3 100644 --- a/composer.json +++ b/composer.json @@ -71,14 +71,11 @@ "phpstan": [ "phpstan analyse --configuration=./ci/phpstan/config.neon" ], - "phpunit": [ - "XDEBUG_MODE=${XDEBUG_MODE:-coverage} phpunit" - ], "phpunit:unit": [ - "@phpunit --configuration=./ci/phpunit/config-unit.xml" + "XDEBUG_MODE=${XDEBUG_MODE:-coverage} phpunit --configuration=./ci/phpunit/config-unit.xml" ], "phpunit:integration": [ - "@phpunit --configuration=./ci/phpunit/config-integration.xml" + "XDEBUG_MODE=${XDEBUG_MODE:-coverage} phpunit --configuration=./ci/phpunit/config-integration.xml" ], "rector": [ "rector --config=./ci/rector/config.php --ansi --no-progress-bar" From 3cafe33d98fc7f1ee61ddd0cfa7aae787e0e6a20 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Tue, 21 Oct 2025 21:48:26 +0100 Subject: [PATCH 06/12] Fix PHPUnit coverage warnings in CI - Remove XDEBUG_MODE from composer scripts to avoid redundancy - Update workflows to explicitly set XDEBUG_MODE=coverage when coverage is needed - Use --no-coverage flag when xdebug is not installed to prevent warnings - This fixes 'No code coverage driver available' warning causing CI failures --- .github/workflows/integration-tests.yml | 8 ++++++-- .github/workflows/unit-tests.yml | 9 ++++++--- composer.json | 4 ++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index e7e4d782..4e319a02 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -130,14 +130,18 @@ jobs: PGPASSWORD=postgres psql -h localhost -U postgres -d postgres_doctrine_test -c "SELECT PostGIS_Version();" - name: Run integration test suite - run: composer run-integration-tests + run: | + if [ "${{ matrix.calculate-code-coverage }}" = "true" ]; then + XDEBUG_MODE=coverage composer run-integration-tests + else + composer run-integration-tests -- --no-coverage + fi env: POSTGRES_HOST: localhost POSTGRES_PORT: 5432 POSTGRES_DB: postgres_doctrine_test POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres - XDEBUG_MODE: ${{ matrix.calculate-code-coverage == true && 'coverage' || 'off' }} - name: Upload coverage results to Coveralls if: matrix.calculate-code-coverage == true diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 5433420c..1d56c7d7 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -110,9 +110,12 @@ jobs: continue-on-error: ${{ matrix.continue-on-error || false }} - name: Run unit test suite - run: composer run-unit-tests - env: - XDEBUG_MODE: ${{ matrix.calculate-code-coverage == true && 'coverage' || 'off' }} + run: | + if [ "${{ matrix.calculate-code-coverage }}" = "true" ]; then + XDEBUG_MODE=coverage composer run-unit-tests + else + composer run-unit-tests -- --no-coverage + fi - name: Upload coverage results to Coveralls if: matrix.calculate-code-coverage == true diff --git a/composer.json b/composer.json index 9dade7c3..a7d5a4e3 100644 --- a/composer.json +++ b/composer.json @@ -72,10 +72,10 @@ "phpstan analyse --configuration=./ci/phpstan/config.neon" ], "phpunit:unit": [ - "XDEBUG_MODE=${XDEBUG_MODE:-coverage} phpunit --configuration=./ci/phpunit/config-unit.xml" + "phpunit --configuration=./ci/phpunit/config-unit.xml" ], "phpunit:integration": [ - "XDEBUG_MODE=${XDEBUG_MODE:-coverage} phpunit --configuration=./ci/phpunit/config-integration.xml" + "phpunit --configuration=./ci/phpunit/config-integration.xml" ], "rector": [ "rector --config=./ci/rector/config.php --ansi --no-progress-bar" From cf3b9bf8441a9980192bdefbdaec44fdc35c9ed2 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Tue, 21 Oct 2025 22:48:42 +0100 Subject: [PATCH 07/12] Fix Scrutinizer CI by explicitly setting XDEBUG_MODE=coverage - Scrutinizer needs XDEBUG_MODE=coverage to generate coverage reports - This aligns with the GitHub Actions workflow approach --- .scrutinizer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 627db6b8..77c7aaab 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -8,7 +8,7 @@ build: tests: override: - php-scrutinizer-run - - command: composer run-unit-tests + - command: XDEBUG_MODE=coverage composer run-unit-tests coverage: file: var/logs/test-coverage/unit/clover.xml format: clover From 54aac5488c24edfe99bc7ced8e1cfd5093003cb2 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Thu, 23 Oct 2025 00:03:15 +0100 Subject: [PATCH 08/12] feat: add support for `uuid_extract_timestamp` and `uuid_extract_version` --- docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md | 2 + docs/MATHEMATICAL-FUNCTIONS.md | 4 ++ .../ORM/Query/AST/Functions/ToNumber.php | 4 +- .../AST/Functions/UuidExtractTimestamp.php | 22 +++++++ .../AST/Functions/UuidExtractVersion.php | 22 +++++++ .../ORM/Query/AST/Functions/ToNumberTest.php | 20 ++++++ .../Functions/UuidExtractTimestampTest.php | 62 +++++++++++++++++++ .../AST/Functions/UuidExtractVersionTest.php | 60 ++++++++++++++++++ .../ORM/Query/AST/Functions/ToNumberTest.php | 2 + .../Functions/UuidExtractTimestampTest.php | 32 ++++++++++ .../AST/Functions/UuidExtractVersionTest.php | 32 ++++++++++ 11 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestamp.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractVersion.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractVersionTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractVersionTest.php diff --git a/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md b/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md index 8eb697ef..467a4e4b 100644 --- a/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md +++ b/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md @@ -102,6 +102,8 @@ Complete documentation for PostgreSQL ltree (label tree) operations and hierarch - `CRC32` - CRC-32 checksum computation - `CRC32C` - CRC-32C checksum computation - `REVERSE_BYTES` - Reverse byte order for bytea values +- `UUID_EXTRACT_TIMESTAMP` - Extract timestamp from UUID v1 or v7 +- `UUID_EXTRACT_VERSION` - Extract version number from UUID - `UUIDV4` - Explicit UUID version 4 generation - `UUIDV7` - Generate timestamp-ordered UUIDs (version 7) for better database performance diff --git a/docs/MATHEMATICAL-FUNCTIONS.md b/docs/MATHEMATICAL-FUNCTIONS.md index db7a70b3..750395a5 100644 --- a/docs/MATHEMATICAL-FUNCTIONS.md +++ b/docs/MATHEMATICAL-FUNCTIONS.md @@ -36,6 +36,8 @@ This document covers PostgreSQL mathematical, utility, and miscellaneous functio | to_char | TO_CHAR | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToChar` | | to_number | TO_NUMBER | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToNumber` | +**Note**: `TO_NUMBER` supports Roman numeral conversion via the `RN` pattern (PostgreSQL 18+). + ## Utility and Miscellaneous Functions | PostgreSQL functions | Register for DQL as | Implemented by | @@ -46,6 +48,8 @@ This document covers PostgreSQL mathematical, utility, and miscellaneous functio | reverse (bytea) | REVERSE_BYTES | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ReverseBytes` | | row | ROW | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Row` | | row_to_json | ROW_TO_JSON | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RowToJson` | +| uuid_extract_timestamp | UUID_EXTRACT_TIMESTAMP | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\UuidExtractTimestamp` | +| uuid_extract_version | UUID_EXTRACT_VERSION | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\UuidExtractVersion` | | uuidv4 | UUIDV4 | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Uuidv4` | | uuidv7 | UUIDV7 | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Uuidv7` | | xmlagg | XML_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\XmlAgg` | diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumber.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumber.php index d528c819..754452c8 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumber.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumber.php @@ -7,7 +7,9 @@ /** * Implementation of PostgreSQL to_number(). * - * @see https://www.postgresql.org/docs/17/functions-formatting.html + * Supports Roman numeral conversion via RN pattern (PostgreSQL 18+). + * + * @see https://www.postgresql.org/docs/18/functions-formatting.html * @since 3.3.0 */ class ToNumber extends BaseFunction diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestamp.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestamp.php new file mode 100644 index 00000000..1322ba61 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestamp.php @@ -0,0 +1,22 @@ + + */ +class UuidExtractTimestamp extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('uuid_extract_timestamp(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractVersion.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractVersion.php new file mode 100644 index 00000000..77e9ae3e --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractVersion.php @@ -0,0 +1,22 @@ + + */ +class UuidExtractVersion extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('uuid_extract_version(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} 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 49fcd9c4..899a1760 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php @@ -26,6 +26,26 @@ public function tonumber(): void $this->assertSame('-12454.8', $result[0]['result']); } + #[Test] + public function tonumber_converts_roman_numerals(): void + { + $this->requirePostgresVersion(180000, 'Roman numeral support in to_number'); + + $dql = "SELECT to_number('MCMXCIV', 'RN') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertSame('1994', $result[0]['result']); + } + + #[Test] + public function tonumber_converts_lowercase_roman_numerals(): void + { + $this->requirePostgresVersion(180000, 'Roman numeral support in to_number'); + + $dql = "SELECT to_number('xlii', 'rn') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertSame('42', $result[0]['result']); + } + #[Test] public function tonumber_throws_with_invalid_format(): void { diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php new file mode 100644 index 00000000..73e43033 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php @@ -0,0 +1,62 @@ +requirePostgresVersion(170000, 'uuid_extract_timestamp function'); + } + + protected function getStringFunctions(): array + { + return [ + 'UUID_EXTRACT_TIMESTAMP' => UuidExtractTimestamp::class, + ]; + } + + #[Test] + public function can_extract_timestamp_from_uuid_v1(): void + { + $dql = "SELECT UUID_EXTRACT_TIMESTAMP('a0eebc99-9c0b-11d1-b465-00c04fd430c8') as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t + WHERE t.id = 1"; + + $result = $this->executeDqlQuery($dql); + $timestamp = $result[0]['result']; + + $this->assertSame('1997-02-03 17:43:12.219+00', $timestamp); + } + + #[Test] + public function can_extract_timestamp_from_uuid_v7(): void + { + $dql = "SELECT UUID_EXTRACT_TIMESTAMP('018e7e39-9f42-7000-8000-000000000000') as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t + WHERE t.id = 1"; + + $result = $this->executeDqlQuery($dql); + $timestamp = $result[0]['result']; + + $this->assertSame('2024-03-15 14:27:30.114+00', $timestamp); + } + + #[Test] + public function returns_null_for_non_timestamped_uuid(): void + { + $dql = "SELECT UUID_EXTRACT_TIMESTAMP('550e8400-e29b-41d4-a716-446655440000') as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t + WHERE t.id = 1"; + + $result = $this->executeDqlQuery($dql); + + $this->assertNull($result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractVersionTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractVersionTest.php new file mode 100644 index 00000000..a34f11b0 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractVersionTest.php @@ -0,0 +1,60 @@ +requirePostgresVersion(170000, 'uuid_extract_version function'); + } + + protected function getStringFunctions(): array + { + return [ + 'UUID_EXTRACT_VERSION' => UuidExtractVersion::class, + ]; + } + + #[Test] + public function can_extract_version_from_uuid_v1(): void + { + $dql = "SELECT UUID_EXTRACT_VERSION('a0eebc99-9c0b-11d1-b465-00c04fd430c8') as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t + WHERE t.id = 1"; + + $result = $this->executeDqlQuery($dql); + + $this->assertSame('1', $result[0]['result']); + } + + #[Test] + public function can_extract_version_from_uuid_v4(): void + { + $dql = "SELECT UUID_EXTRACT_VERSION('550e8400-e29b-41d4-a716-446655440000') as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t + WHERE t.id = 1"; + + $result = $this->executeDqlQuery($dql); + + $this->assertSame('4', $result[0]['result']); + } + + #[Test] + public function can_extract_version_from_uuid_v7(): void + { + $dql = "SELECT UUID_EXTRACT_VERSION('018e7e39-9f42-7000-8000-000000000000') as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t + WHERE t.id = 1"; + + $result = $this->executeDqlQuery($dql); + + $this->assertSame('7', $result[0]['result']); + } +} 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 1e7ab224..72ce568e 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php @@ -22,6 +22,7 @@ protected function getExpectedSqlStatements(): array { return [ 'converts text to number using format pattern' => "SELECT to_number(c0_.text1, '99G999D9S') AS sclr_0 FROM ContainsTexts c0_", + 'converts roman numerals to number' => "SELECT to_number(c0_.text1, 'RN') AS sclr_0 FROM ContainsTexts c0_", ]; } @@ -29,6 +30,7 @@ protected function getDqlStatements(): array { return [ 'converts text to number using format pattern' => \sprintf("SELECT TO_NUMBER(e.text1, '99G999D9S') FROM %s e", ContainsTexts::class), + 'converts roman numerals to number' => \sprintf("SELECT TO_NUMBER(e.text1, 'RN') FROM %s e", ContainsTexts::class), ]; } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php new file mode 100644 index 00000000..c723a43b --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php @@ -0,0 +1,32 @@ + UuidExtractTimestamp::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'extracts timestamp from uuid' => 'SELECT uuid_extract_timestamp(c0_.text1) AS sclr_0 FROM ContainsTexts c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'extracts timestamp from uuid' => \sprintf('SELECT UUID_EXTRACT_TIMESTAMP(e.text1) FROM %s e', ContainsTexts::class), + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractVersionTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractVersionTest.php new file mode 100644 index 00000000..ee93fc2d --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractVersionTest.php @@ -0,0 +1,32 @@ + UuidExtractVersion::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'extracts version from uuid' => 'SELECT uuid_extract_version(c0_.text1) AS sclr_0 FROM ContainsTexts c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'extracts version from uuid' => \sprintf('SELECT UUID_EXTRACT_VERSION(e.text1) FROM %s e', ContainsTexts::class), + ]; + } +} From 8cb6321970afae974359ee8a284dc7f693e70a4f Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Thu, 23 Oct 2025 00:23:22 +0100 Subject: [PATCH 09/12] no message --- .../AST/Functions/UuidExtractTimestampTest.php | 4 ++-- .../AST/Functions/UuidExtractVersionTest.php | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php index 73e43033..12112a03 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php @@ -32,7 +32,7 @@ public function can_extract_timestamp_from_uuid_v1(): void $result = $this->executeDqlQuery($dql); $timestamp = $result[0]['result']; - $this->assertSame('1997-02-03 17:43:12.219+00', $timestamp); + $this->assertStringStartsWith('1998-02-02 20:23:12.90287', $timestamp); } #[Test] @@ -45,7 +45,7 @@ public function can_extract_timestamp_from_uuid_v7(): void $result = $this->executeDqlQuery($dql); $timestamp = $result[0]['result']; - $this->assertSame('2024-03-15 14:27:30.114+00', $timestamp); + $this->assertStringStartsWith('2024-03-27 04:44:49.346', $timestamp); } #[Test] diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractVersionTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractVersionTest.php index a34f11b0..b604ca02 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractVersionTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractVersionTest.php @@ -25,36 +25,36 @@ protected function getStringFunctions(): array #[Test] public function can_extract_version_from_uuid_v1(): void { - $dql = "SELECT UUID_EXTRACT_VERSION('a0eebc99-9c0b-11d1-b465-00c04fd430c8') as result - FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t + $dql = "SELECT UUID_EXTRACT_VERSION('a0eebc99-9c0b-11d1-b465-00c04fd430c8') as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t WHERE t.id = 1"; $result = $this->executeDqlQuery($dql); - $this->assertSame('1', $result[0]['result']); + $this->assertEquals(1, $result[0]['result']); } #[Test] public function can_extract_version_from_uuid_v4(): void { - $dql = "SELECT UUID_EXTRACT_VERSION('550e8400-e29b-41d4-a716-446655440000') as result - FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t + $dql = "SELECT UUID_EXTRACT_VERSION('550e8400-e29b-41d4-a716-446655440000') as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t WHERE t.id = 1"; $result = $this->executeDqlQuery($dql); - $this->assertSame('4', $result[0]['result']); + $this->assertEquals(4, $result[0]['result']); } #[Test] public function can_extract_version_from_uuid_v7(): void { - $dql = "SELECT UUID_EXTRACT_VERSION('018e7e39-9f42-7000-8000-000000000000') as result - FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t + $dql = "SELECT UUID_EXTRACT_VERSION('018e7e39-9f42-7000-8000-000000000000') as result + FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t WHERE t.id = 1"; $result = $this->executeDqlQuery($dql); - $this->assertSame('7', $result[0]['result']); + $this->assertEquals(7, $result[0]['result']); } } From 6af99ffef5026d634ab7cb45cdefc46b4fd27ad8 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Thu, 23 Oct 2025 00:28:33 +0100 Subject: [PATCH 10/12] no message --- .../ORM/Query/AST/Functions/UuidExtractTimestampTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php index 12112a03..840a1d2f 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php @@ -31,6 +31,7 @@ public function can_extract_timestamp_from_uuid_v1(): void $result = $this->executeDqlQuery($dql); $timestamp = $result[0]['result']; + \assert(\is_string($timestamp)); $this->assertStringStartsWith('1998-02-02 20:23:12.90287', $timestamp); } @@ -44,6 +45,7 @@ public function can_extract_timestamp_from_uuid_v7(): void $result = $this->executeDqlQuery($dql); $timestamp = $result[0]['result']; + \assert(\is_string($timestamp)); $this->assertStringStartsWith('2024-03-27 04:44:49.346', $timestamp); } From 452ad1ce0697e30012ad459450850be339b4b227 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Thu, 23 Oct 2025 00:32:02 +0100 Subject: [PATCH 11/12] no message --- src/MartinGeorgiev/Doctrine/DBAL/Types/BooleanArray.php | 2 +- src/MartinGeorgiev/Utils/PHPArrayToPostgresValueTransformer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/BooleanArray.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/BooleanArray.php index b9cf57cd..6895f1ce 100644 --- a/src/MartinGeorgiev/Doctrine/DBAL/Types/BooleanArray.php +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/BooleanArray.php @@ -41,6 +41,6 @@ public function convertToPHPValue($postgresArray, AbstractPlatform $platform): ? return null; } - return \array_map(static fn ($value): ?bool => $platform->convertFromBoolean($value), $phpArray); + return \array_map($platform->convertFromBoolean(...), $phpArray); } } diff --git a/src/MartinGeorgiev/Utils/PHPArrayToPostgresValueTransformer.php b/src/MartinGeorgiev/Utils/PHPArrayToPostgresValueTransformer.php index 705e8dd3..caa540e5 100644 --- a/src/MartinGeorgiev/Utils/PHPArrayToPostgresValueTransformer.php +++ b/src/MartinGeorgiev/Utils/PHPArrayToPostgresValueTransformer.php @@ -36,7 +36,7 @@ public static function transformToPostgresTextArray(array $phpArray): string /** @var array */ $processed = \array_map( - static fn (mixed $value): string => self::formatValue($value), + self::formatValue(...), $phpArray ); From cae582da17c9ae92671cbd9f54effc806f9396dc Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Thu, 23 Oct 2025 09:39:03 +0100 Subject: [PATCH 12/12] no message --- .../ORM/Query/AST/Functions/UuidExtractTimestampTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php index 840a1d2f..7d183930 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/UuidExtractTimestampTest.php @@ -39,6 +39,8 @@ public function can_extract_timestamp_from_uuid_v1(): void #[Test] public function can_extract_timestamp_from_uuid_v7(): void { + $this->requirePostgresVersion(180000, 'uuid_extract_timestamp function for UUID v7'); + $dql = "SELECT UUID_EXTRACT_TIMESTAMP('018e7e39-9f42-7000-8000-000000000000') as result FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t WHERE t.id = 1";