Skip to content

Commit 77a513f

Browse files
feat: add support for uuid_extract_timestamp and uuid_extract_version (#466)
1 parent 976647b commit 77a513f

File tree

13 files changed

+267
-3
lines changed

13 files changed

+267
-3
lines changed

docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ Complete documentation for PostgreSQL ltree (label tree) operations and hierarch
102102
- `CRC32` - CRC-32 checksum computation
103103
- `CRC32C` - CRC-32C checksum computation
104104
- `REVERSE_BYTES` - Reverse byte order for bytea values
105+
- `UUID_EXTRACT_TIMESTAMP` - Extract timestamp from UUID v1 or v7
106+
- `UUID_EXTRACT_VERSION` - Extract version number from UUID
105107
- `UUIDV4` - Explicit UUID version 4 generation
106108
- `UUIDV7` - Generate timestamp-ordered UUIDs (version 7) for better database performance
107109

docs/MATHEMATICAL-FUNCTIONS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ This document covers PostgreSQL mathematical, utility, and miscellaneous functio
3636
| to_char | TO_CHAR | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToChar` |
3737
| to_number | TO_NUMBER | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToNumber` |
3838

39+
**Note**: `TO_NUMBER` supports Roman numeral conversion via the `RN` pattern (PostgreSQL 18+).
40+
3941
## Utility and Miscellaneous Functions
4042

4143
| PostgreSQL functions | Register for DQL as | Implemented by |
@@ -46,6 +48,8 @@ This document covers PostgreSQL mathematical, utility, and miscellaneous functio
4648
| reverse (bytea) | REVERSE_BYTES | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ReverseBytes` |
4749
| row | ROW | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Row` |
4850
| row_to_json | ROW_TO_JSON | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RowToJson` |
51+
| uuid_extract_timestamp | UUID_EXTRACT_TIMESTAMP | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\UuidExtractTimestamp` |
52+
| uuid_extract_version | UUID_EXTRACT_VERSION | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\UuidExtractVersion` |
4953
| uuidv4 | UUIDV4 | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Uuidv4` |
5054
| uuidv7 | UUIDV7 | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Uuidv7` |
5155
| xmlagg | XML_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\XmlAgg` |

src/MartinGeorgiev/Doctrine/DBAL/Types/BooleanArray.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@ public function convertToPHPValue($postgresArray, AbstractPlatform $platform): ?
4141
return null;
4242
}
4343

44-
return \array_map(static fn ($value): ?bool => $platform->convertFromBoolean($value), $phpArray);
44+
return \array_map($platform->convertFromBoolean(...), $phpArray);
4545
}
4646
}

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumber.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
/**
88
* Implementation of PostgreSQL to_number().
99
*
10-
* @see https://www.postgresql.org/docs/17/functions-formatting.html
10+
* Supports Roman numeral conversion via RN pattern (PostgreSQL 18+).
11+
*
12+
* @see https://www.postgresql.org/docs/18/functions-formatting.html
1113
* @since 3.3.0
1214
*/
1315
class ToNumber extends BaseFunction
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
/**
8+
* Implementation of PostgreSQL UUID_EXTRACT_TIMESTAMP().
9+
*
10+
* @see https://www.postgresql.org/docs/17/functions-uuid.html
11+
* @since 3.6
12+
*
13+
* @author Martin Georgiev <martin.georgiev@gmail.com>
14+
*/
15+
class UuidExtractTimestamp extends BaseFunction
16+
{
17+
protected function customizeFunction(): void
18+
{
19+
$this->setFunctionPrototype('uuid_extract_timestamp(%s)');
20+
$this->addNodeMapping('StringPrimary');
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
/**
8+
* Implementation of PostgreSQL UUID_EXTRACT_VERSION().
9+
*
10+
* @see https://www.postgresql.org/docs/17/functions-uuid.html
11+
* @since 3.6
12+
*
13+
* @author Martin Georgiev <martin.georgiev@gmail.com>
14+
*/
15+
class UuidExtractVersion extends BaseFunction
16+
{
17+
protected function customizeFunction(): void
18+
{
19+
$this->setFunctionPrototype('uuid_extract_version(%s)');
20+
$this->addNodeMapping('StringPrimary');
21+
}
22+
}

src/MartinGeorgiev/Utils/PHPArrayToPostgresValueTransformer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public static function transformToPostgresTextArray(array $phpArray): string
3636

3737
/** @var array<int|string, string> */
3838
$processed = \array_map(
39-
static fn (mixed $value): string => self::formatValue($value),
39+
self::formatValue(...),
4040
$phpArray
4141
);
4242

tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ToNumberTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,26 @@ public function tonumber(): void
2626
$this->assertSame('-12454.8', $result[0]['result']);
2727
}
2828

29+
#[Test]
30+
public function tonumber_converts_roman_numerals(): void
31+
{
32+
$this->requirePostgresVersion(180000, 'Roman numeral support in to_number');
33+
34+
$dql = "SELECT to_number('MCMXCIV', 'RN') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1";
35+
$result = $this->executeDqlQuery($dql);
36+
$this->assertSame('1994', $result[0]['result']);
37+
}
38+
39+
#[Test]
40+
public function tonumber_converts_lowercase_roman_numerals(): void
41+
{
42+
$this->requirePostgresVersion(180000, 'Roman numeral support in to_number');
43+
44+
$dql = "SELECT to_number('xlii', 'rn') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsTexts t WHERE t.id = 1";
45+
$result = $this->executeDqlQuery($dql);
46+
$this->assertSame('42', $result[0]['result']);
47+
}
48+
2949
#[Test]
3050
public function tonumber_throws_with_invalid_format(): void
3151
{
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Integration\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\UuidExtractTimestamp;
8+
use PHPUnit\Framework\Attributes\Test;
9+
10+
class UuidExtractTimestampTest extends NumericTestCase
11+
{
12+
protected function setUp(): void
13+
{
14+
parent::setUp();
15+
$this->requirePostgresVersion(170000, 'uuid_extract_timestamp function');
16+
}
17+
18+
protected function getStringFunctions(): array
19+
{
20+
return [
21+
'UUID_EXTRACT_TIMESTAMP' => UuidExtractTimestamp::class,
22+
];
23+
}
24+
25+
#[Test]
26+
public function can_extract_timestamp_from_uuid_v1(): void
27+
{
28+
$dql = "SELECT UUID_EXTRACT_TIMESTAMP('a0eebc99-9c0b-11d1-b465-00c04fd430c8') as result
29+
FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t
30+
WHERE t.id = 1";
31+
32+
$result = $this->executeDqlQuery($dql);
33+
$timestamp = $result[0]['result'];
34+
\assert(\is_string($timestamp));
35+
36+
$this->assertStringStartsWith('1998-02-02 20:23:12.90287', $timestamp);
37+
}
38+
39+
#[Test]
40+
public function can_extract_timestamp_from_uuid_v7(): void
41+
{
42+
$this->requirePostgresVersion(180000, 'uuid_extract_timestamp function for UUID v7');
43+
44+
$dql = "SELECT UUID_EXTRACT_TIMESTAMP('018e7e39-9f42-7000-8000-000000000000') as result
45+
FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t
46+
WHERE t.id = 1";
47+
48+
$result = $this->executeDqlQuery($dql);
49+
$timestamp = $result[0]['result'];
50+
\assert(\is_string($timestamp));
51+
52+
$this->assertStringStartsWith('2024-03-27 04:44:49.346', $timestamp);
53+
}
54+
55+
#[Test]
56+
public function returns_null_for_non_timestamped_uuid(): void
57+
{
58+
$dql = "SELECT UUID_EXTRACT_TIMESTAMP('550e8400-e29b-41d4-a716-446655440000') as result
59+
FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t
60+
WHERE t.id = 1";
61+
62+
$result = $this->executeDqlQuery($dql);
63+
64+
$this->assertNull($result[0]['result']);
65+
}
66+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Integration\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\UuidExtractVersion;
8+
use PHPUnit\Framework\Attributes\Test;
9+
10+
class UuidExtractVersionTest extends NumericTestCase
11+
{
12+
protected function setUp(): void
13+
{
14+
parent::setUp();
15+
$this->requirePostgresVersion(170000, 'uuid_extract_version function');
16+
}
17+
18+
protected function getStringFunctions(): array
19+
{
20+
return [
21+
'UUID_EXTRACT_VERSION' => UuidExtractVersion::class,
22+
];
23+
}
24+
25+
#[Test]
26+
public function can_extract_version_from_uuid_v1(): void
27+
{
28+
$dql = "SELECT UUID_EXTRACT_VERSION('a0eebc99-9c0b-11d1-b465-00c04fd430c8') as result
29+
FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t
30+
WHERE t.id = 1";
31+
32+
$result = $this->executeDqlQuery($dql);
33+
34+
$this->assertEquals(1, $result[0]['result']);
35+
}
36+
37+
#[Test]
38+
public function can_extract_version_from_uuid_v4(): void
39+
{
40+
$dql = "SELECT UUID_EXTRACT_VERSION('550e8400-e29b-41d4-a716-446655440000') as result
41+
FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t
42+
WHERE t.id = 1";
43+
44+
$result = $this->executeDqlQuery($dql);
45+
46+
$this->assertEquals(4, $result[0]['result']);
47+
}
48+
49+
#[Test]
50+
public function can_extract_version_from_uuid_v7(): void
51+
{
52+
$dql = "SELECT UUID_EXTRACT_VERSION('018e7e39-9f42-7000-8000-000000000000') as result
53+
FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t
54+
WHERE t.id = 1";
55+
56+
$result = $this->executeDqlQuery($dql);
57+
58+
$this->assertEquals(7, $result[0]['result']);
59+
}
60+
}

0 commit comments

Comments
 (0)