Skip to content

Commit 54aac54

Browse files
feat: add support for uuid_extract_timestamp and uuid_extract_version
1 parent 511d569 commit 54aac54

File tree

11 files changed

+261
-1
lines changed

11 files changed

+261
-1
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/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+
}

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: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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+
35+
$this->assertSame('1997-02-03 17:43:12.219+00', $timestamp);
36+
}
37+
38+
#[Test]
39+
public function can_extract_timestamp_from_uuid_v7(): void
40+
{
41+
$dql = "SELECT UUID_EXTRACT_TIMESTAMP('018e7e39-9f42-7000-8000-000000000000') as result
42+
FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t
43+
WHERE t.id = 1";
44+
45+
$result = $this->executeDqlQuery($dql);
46+
$timestamp = $result[0]['result'];
47+
48+
$this->assertSame('2024-03-15 14:27:30.114+00', $timestamp);
49+
}
50+
51+
#[Test]
52+
public function returns_null_for_non_timestamped_uuid(): void
53+
{
54+
$dql = "SELECT UUID_EXTRACT_TIMESTAMP('550e8400-e29b-41d4-a716-446655440000') as result
55+
FROM Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsNumerics t
56+
WHERE t.id = 1";
57+
58+
$result = $this->executeDqlQuery($dql);
59+
60+
$this->assertNull($result[0]['result']);
61+
}
62+
}
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->assertSame('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->assertSame('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->assertSame('7', $result[0]['result']);
59+
}
60+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ protected function getExpectedSqlStatements(): array
2222
{
2323
return [
2424
'converts text to number using format pattern' => "SELECT to_number(c0_.text1, '99G999D9S') AS sclr_0 FROM ContainsTexts c0_",
25+
'converts roman numerals to number' => "SELECT to_number(c0_.text1, 'RN') AS sclr_0 FROM ContainsTexts c0_",
2526
];
2627
}
2728

2829
protected function getDqlStatements(): array
2930
{
3031
return [
3132
'converts text to number using format pattern' => \sprintf("SELECT TO_NUMBER(e.text1, '99G999D9S') FROM %s e", ContainsTexts::class),
33+
'converts roman numerals to number' => \sprintf("SELECT TO_NUMBER(e.text1, 'RN') FROM %s e", ContainsTexts::class),
3234
];
3335
}
3436

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsTexts;
8+
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\UuidExtractTimestamp;
9+
10+
class UuidExtractTimestampTest extends TestCase
11+
{
12+
protected function getStringFunctions(): array
13+
{
14+
return [
15+
'UUID_EXTRACT_TIMESTAMP' => UuidExtractTimestamp::class,
16+
];
17+
}
18+
19+
protected function getExpectedSqlStatements(): array
20+
{
21+
return [
22+
'extracts timestamp from uuid' => 'SELECT uuid_extract_timestamp(c0_.text1) AS sclr_0 FROM ContainsTexts c0_',
23+
];
24+
}
25+
26+
protected function getDqlStatements(): array
27+
{
28+
return [
29+
'extracts timestamp from uuid' => \sprintf('SELECT UUID_EXTRACT_TIMESTAMP(e.text1) FROM %s e', ContainsTexts::class),
30+
];
31+
}
32+
}

0 commit comments

Comments
 (0)