Skip to content

Commit 287d53c

Browse files
authored
Merge pull request #3 from dmstr/feature/postgres-partial-uuid
fix(uuid): PostgreSQL-compatible partial-UUID resolution
2 parents 9152011 + e44f413 commit 287d53c

2 files changed

Lines changed: 75 additions & 3 deletions

File tree

src/Service/UuidResolver.php

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
namespace Dmstr\ApiPlatformUtils\Service;
77

8+
use Doctrine\DBAL\Platforms\AbstractPlatform;
9+
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
810
use Doctrine\ORM\EntityManagerInterface;
911
use Symfony\Component\Uid\Uuid;
1012

@@ -59,17 +61,20 @@ public function findByPartialUuid(string $entityClass, string $partialId): ?obje
5961
}
6062

6163
/**
62-
* Find entity with binary UUID storage using native SQL
64+
* Find entity with UUID storage using native SQL.
6365
*
64-
* Uses HEX(id) to convert binary UUID to string for LIKE matching
66+
* The Symfony `uuid` type stores the value differently per platform — a
67+
* BINARY(16) on MySQL/MariaDB, a native `uuid` column on PostgreSQL — so the
68+
* partial-prefix match must be built per platform (see {@see buildPartialUuidSql}).
6569
*/
6670
private function findByPartialBinaryUuid(string $entityClass, string $partialId): ?object
6771
{
6872
$metadata = $this->entityManager->getClassMetadata($entityClass);
6973
$tableName = $metadata->getTableName();
74+
$idColumn = $metadata->getColumnName('id');
7075

7176
$conn = $this->entityManager->getConnection();
72-
$sql = "SELECT BIN_TO_UUID(id) as uuid_str FROM {$tableName} WHERE LOWER(HEX(id)) LIKE :partialId LIMIT 2";
77+
$sql = self::buildPartialUuidSql($conn->getDatabasePlatform(), $tableName, $idColumn);
7378
$stmt = $conn->prepare($sql);
7479
$stmt->bindValue('partialId', strtolower(str_replace('-', '', $partialId)) . '%');
7580
$resultSet = $stmt->executeQuery();
@@ -89,6 +94,35 @@ private function findByPartialBinaryUuid(string $entityClass, string $partialId)
8994
return $this->entityManager->getRepository($entityClass)->find($fullUuid);
9095
}
9196

97+
/**
98+
* Build the native partial-UUID lookup SQL for the given platform.
99+
*
100+
* Both branches select the canonical hyphenated UUID string as `uuid_str`
101+
* and match the caller's hyphen-stripped, lower-cased hex prefix:
102+
* - PostgreSQL: the column is a native `uuid`; cast to text and strip the
103+
* hyphens (`REPLACE(LOWER(id::text), '-', '')`).
104+
* - MySQL/MariaDB: the column is BINARY(16); `HEX()` yields the 32-char hex
105+
* and `BIN_TO_UUID()` reads it back as a canonical string.
106+
*
107+
* Public + static so it can be unit-tested per platform without a database.
108+
*/
109+
public static function buildPartialUuidSql(AbstractPlatform $platform, string $tableName, string $idColumn): string
110+
{
111+
if ($platform instanceof PostgreSQLPlatform) {
112+
return sprintf(
113+
"SELECT %2\$s::text AS uuid_str FROM %1\$s WHERE REPLACE(LOWER(%2\$s::text), '-', '') LIKE :partialId LIMIT 2",
114+
$tableName,
115+
$idColumn
116+
);
117+
}
118+
119+
return sprintf(
120+
'SELECT BIN_TO_UUID(%2$s) AS uuid_str FROM %1$s WHERE LOWER(HEX(%2$s)) LIKE :partialId LIMIT 2',
121+
$tableName,
122+
$idColumn
123+
);
124+
}
125+
92126
/**
93127
* Find entity with string UUID storage using DQL
94128
*

tests/Service/UuidResolverTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
// file generated with AI assistance: Claude Code - 2026-07-01 15:25:00 UTC
3+
4+
declare(strict_types=1);
5+
6+
namespace Dmstr\ApiPlatformUtils\Tests\Service;
7+
8+
use Dmstr\ApiPlatformUtils\Service\UuidResolver;
9+
use Doctrine\DBAL\Platforms\MySQL80Platform;
10+
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
11+
use PHPUnit\Framework\TestCase;
12+
13+
/**
14+
* The partial-UUID lookup SQL must match the platform's UUID storage:
15+
* BINARY(16) + HEX()/BIN_TO_UUID() on MySQL, native `uuid` + `::text` on
16+
* PostgreSQL. A MySQL-only query threw `function bin_to_uuid(uuid) does not
17+
* exist` on PostgreSQL; these tests guard against a regression.
18+
*/
19+
final class UuidResolverTest extends TestCase
20+
{
21+
public function testPostgresSqlCastsToTextAndAvoidsMysqlFunctions(): void
22+
{
23+
$sql = UuidResolver::buildPartialUuidSql(new PostgreSQLPlatform(), 'za7_api_configuration', 'id');
24+
25+
self::assertStringContainsString('id::text', $sql);
26+
self::assertStringContainsString("REPLACE(LOWER(id::text), '-', '')", $sql);
27+
self::assertStringNotContainsString('BIN_TO_UUID', $sql);
28+
self::assertStringNotContainsString('HEX(', $sql);
29+
}
30+
31+
public function testMysqlSqlUsesHexFunctions(): void
32+
{
33+
$sql = UuidResolver::buildPartialUuidSql(new MySQL80Platform(), 'za7_api_configuration', 'id');
34+
35+
self::assertStringContainsString('BIN_TO_UUID(id)', $sql);
36+
self::assertStringContainsString('HEX(id)', $sql);
37+
}
38+
}

0 commit comments

Comments
 (0)