From ee901ce92c1d41cb55d6d3114dabd8f3567fb312 Mon Sep 17 00:00:00 2001 From: zoran Date: Tue, 8 Sep 2026 07:16:20 +0200 Subject: [PATCH 1/3] New: Add SQLite implementation of `UuidValue` expression SQLite stores a UUID as 16 raw bytes in a `blob(16)` column, so `prepareValue()` converts the canonical form with `DbUuidHelper::uuidToBlob()` and binds it as `DataType::LOB`. Requires yiisoft/db#1199. --- CHANGELOG.md | 1 + src/Builder/UuidValueBuilder.php | 25 +++++++++++ src/DQLQueryBuilder.php | 3 ++ tests/Builder/UuidValueBuilderTest.php | 57 ++++++++++++++++++++++++++ 4 files changed, 86 insertions(+) create mode 100644 src/Builder/UuidValueBuilder.php create mode 100644 tests/Builder/UuidValueBuilderTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d6ee9ea..d78724c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Enh #414: Explicitly import classes and constants in "use" section (@mspirkov) - Enh #415: Remove unnecessary files from Composer package (@mspirkov) - Enh #416: Add `ext-pdo_sqlite` to `require` section of `composer.json` (@Tigrov) +- New #432: Add SQLite implementation of `UuidValue` expression (@KalimeroMK) ## 2.0.0 December 05, 2025 diff --git a/src/Builder/UuidValueBuilder.php b/src/Builder/UuidValueBuilder.php new file mode 100644 index 00000000..e3e8c690 --- /dev/null +++ b/src/Builder/UuidValueBuilder.php @@ -0,0 +1,25 @@ +value), DataType::LOB); + } +} diff --git a/src/DQLQueryBuilder.php b/src/DQLQueryBuilder.php index 808b3a9c..f68d0972 100644 --- a/src/DQLQueryBuilder.php +++ b/src/DQLQueryBuilder.php @@ -9,6 +9,7 @@ use Yiisoft\Db\Expression\Function\ArrayMerge; use Yiisoft\Db\Expression\Function\Greatest; use Yiisoft\Db\Expression\Function\Least; +use Yiisoft\Db\Expression\Value\UuidValue; use Yiisoft\Db\Query\Query; use Yiisoft\Db\Query\QueryInterface; use Yiisoft\Db\QueryBuilder\AbstractDQLQueryBuilder; @@ -23,6 +24,7 @@ use Yiisoft\Db\Sqlite\Builder\JsonOverlapsBuilder; use Yiisoft\Db\Sqlite\Builder\LeastBuilder; use Yiisoft\Db\Sqlite\Builder\LikeBuilder; +use Yiisoft\Db\Sqlite\Builder\UuidValueBuilder; use function array_filter; use function array_merge; @@ -141,6 +143,7 @@ protected function defaultExpressionBuilders(): array ArrayMerge::class => ArrayMergeBuilder::class, Greatest::class => GreatestBuilder::class, Least::class => LeastBuilder::class, + UuidValue::class => UuidValueBuilder::class, ]; } } diff --git a/tests/Builder/UuidValueBuilderTest.php b/tests/Builder/UuidValueBuilderTest.php new file mode 100644 index 00000000..72cbc83a --- /dev/null +++ b/tests/Builder/UuidValueBuilderTest.php @@ -0,0 +1,57 @@ +getSharedConnection(); + $builder = new UuidValueBuilder($db->getQueryBuilder()); + + $params = []; + $result = $builder->build(new UuidValue(self::UUID), $params); + + $this->assertSame(':qp0', $result); + $this->assertEquals( + [':qp0' => new Param(DbUuidHelper::uuidToBlob(self::UUID), DataType::LOB)], + $params, + ); + } + + public function testInsertAndSelectUuid(): void + { + $db = $this->getSharedConnection(); + + $this->dropTable('uuid_value'); + $this->executeStatements('CREATE TABLE [[uuid_value]] ([[id]] blob(16) NOT NULL)'); + + $db->createCommand()->insert('uuid_value', ['id' => new UuidValue(self::UUID)])->execute(); + + $bytes = $db->createCommand('SELECT [[id]] FROM [[uuid_value]]')->queryScalar(); + + $this->assertSame(16, strlen($bytes)); + $this->assertSame(self::UUID, DbUuidHelper::toUuid($bytes)); + + $this->dropTable('uuid_value'); + } +} From 573d018d7cb6ac1746b684bfb8a7041bcd086d7d Mon Sep 17 00:00:00 2001 From: zoran Date: Tue, 8 Sep 2026 08:33:22 +0200 Subject: [PATCH 2/3] Rename the base-class alias and guard the read-back type in the test --- src/Builder/UuidValueBuilder.php | 4 ++-- tests/Builder/UuidValueBuilderTest.php | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Builder/UuidValueBuilder.php b/src/Builder/UuidValueBuilder.php index e3e8c690..9878d2f4 100644 --- a/src/Builder/UuidValueBuilder.php +++ b/src/Builder/UuidValueBuilder.php @@ -5,7 +5,7 @@ namespace Yiisoft\Db\Sqlite\Builder; use Yiisoft\Db\Constant\DataType; -use Yiisoft\Db\Expression\Value\Builder\UuidValueBuilder as AbstractUuidValueBuilder; +use Yiisoft\Db\Expression\Value\Builder\UuidValueBuilder as BaseUuidValueBuilder; use Yiisoft\Db\Expression\Value\Param; use Yiisoft\Db\Expression\Value\UuidValue; use Yiisoft\Db\Helper\DbUuidHelper; @@ -16,7 +16,7 @@ * SQLite stores a UUID as 16 raw bytes in a `blob(16)` column, so the canonical string form is converted to bytes and * bound as {@see DataType::LOB}. */ -final class UuidValueBuilder extends AbstractUuidValueBuilder +final class UuidValueBuilder extends BaseUuidValueBuilder { protected function prepareValue(UuidValue $expression): Param { diff --git a/tests/Builder/UuidValueBuilderTest.php b/tests/Builder/UuidValueBuilderTest.php index 72cbc83a..92a90ded 100644 --- a/tests/Builder/UuidValueBuilderTest.php +++ b/tests/Builder/UuidValueBuilderTest.php @@ -49,6 +49,7 @@ public function testInsertAndSelectUuid(): void $bytes = $db->createCommand('SELECT [[id]] FROM [[uuid_value]]')->queryScalar(); + $this->assertIsString($bytes); $this->assertSame(16, strlen($bytes)); $this->assertSame(self::UUID, DbUuidHelper::toUuid($bytes)); From 7fdf83bc9b804a1bca1bc52610107d114d75410c Mon Sep 17 00:00:00 2001 From: zoran Date: Fri, 11 Sep 2026 14:24:54 +0200 Subject: [PATCH 3/3] Cover every accepted UUID input form in the builder test --- tests/Builder/UuidValueBuilderTest.php | 29 ++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/tests/Builder/UuidValueBuilderTest.php b/tests/Builder/UuidValueBuilderTest.php index 92a90ded..d7d4db83 100644 --- a/tests/Builder/UuidValueBuilderTest.php +++ b/tests/Builder/UuidValueBuilderTest.php @@ -4,6 +4,7 @@ namespace Yiisoft\Db\Sqlite\Tests\Builder; +use PHPUnit\Framework\Attributes\DataProvider; use Yiisoft\Db\Constant\DataType; use Yiisoft\Db\Expression\Value\Param; use Yiisoft\Db\Expression\Value\UuidValue; @@ -12,6 +13,7 @@ use Yiisoft\Db\Sqlite\Tests\Support\IntegrationTestTrait; use Yiisoft\Db\Tests\Support\IntegrationTestCase; +use function hex2bin; use function strlen; /** @@ -22,14 +24,32 @@ final class UuidValueBuilderTest extends IntegrationTestCase use IntegrationTestTrait; private const UUID = '738146be-87b1-49f2-9913-36142fb6fcbe'; + private const HEX = '738146be87b149f2991336142fb6fcbe'; - public function testBuildBindsRawBytesAsLob(): void + /** + * Every form {@see UuidValue} accepts, all denoting the same UUID. + */ + public static function values(): iterable + { + yield 'canonical' => [self::UUID]; + yield 'canonical in upper case' => ['738146BE-87B1-49F2-9913-36142FB6FCBE']; + yield 'hexadecimal' => [self::HEX]; + yield 'hexadecimal in upper case' => ['738146BE87B149F2991336142FB6FCBE']; + yield 'bytes' => [hex2bin(self::HEX)]; + } + + /** + * `UuidValue` normalizes the value to the canonical form on construction, so the builder binds the same 16 bytes + * whichever form it was created from. + */ + #[DataProvider('values')] + public function testBuildBindsRawBytesAsLob(string $value): void { $db = $this->getSharedConnection(); $builder = new UuidValueBuilder($db->getQueryBuilder()); $params = []; - $result = $builder->build(new UuidValue(self::UUID), $params); + $result = $builder->build(new UuidValue($value), $params); $this->assertSame(':qp0', $result); $this->assertEquals( @@ -38,14 +58,15 @@ public function testBuildBindsRawBytesAsLob(): void ); } - public function testInsertAndSelectUuid(): void + #[DataProvider('values')] + public function testInsertAndSelectUuid(string $value): void { $db = $this->getSharedConnection(); $this->dropTable('uuid_value'); $this->executeStatements('CREATE TABLE [[uuid_value]] ([[id]] blob(16) NOT NULL)'); - $db->createCommand()->insert('uuid_value', ['id' => new UuidValue(self::UUID)])->execute(); + $db->createCommand()->insert('uuid_value', ['id' => new UuidValue($value)])->execute(); $bytes = $db->createCommand('SELECT [[id]] FROM [[uuid_value]]')->queryScalar();