Skip to content

New #1152: Add UuidValue expression for DBMS-independent UUID values - #1199

Open
KalimeroMK wants to merge 2 commits into
yiisoft:masterfrom
KalimeroMK:add-1152-uuid-value
Open

New #1152: Add UuidValue expression for DBMS-independent UUID values#1199
KalimeroMK wants to merge 2 commits into
yiisoft:masterfrom
KalimeroMK:add-1152-uuid-value

Conversation

@KalimeroMK

@KalimeroMK KalimeroMK commented Sep 7, 2026

Copy link
Copy Markdown
Contributor
Q A
Is bugfix?
New feature? ✔️
Breaks BC?
Fixed issues #1152

Adds UuidValue, an expression that carries a UUID and is bound in the representation the current DBMS expects — raw bytes for MySQL, MariaDB, SQLite and Oracle, canonical string for PostgreSQL and MSSQL:

$db->createCommand()->insert('{{%page}}', ['id' => new UuidValue(Uuid::uuid7())])->execute();

It accepts the canonical form, 32 hexadecimal characters, 16 raw bytes or any Stringable. Passing raw bytes or a raw string directly keeps working as before.

…values

`ColumnBuilder::uuidPrimaryKey()` and `uuid()` already produce the right DDL
on every DBMS, but inserting a UUID required knowing which representation the
current connection expects: MySQL, MariaDB, SQLite and Oracle store a UUID as
16 raw bytes, while PostgreSQL and MSSQL expect the canonical string. The
official guide had to document the difference (yiisoft/docs#324) and
`CommonCommandTest::testUuid()` works around it with a per-driver `match`.

`UuidValue` carries the intent in the value instead of relying on the loaded
table schema. That matters because a UUID column reads back as `binary(16)`
on MySQL and `blob(16)` on SQLite, so a schema-driven typecast cannot tell it
apart from an ordinary binary column.

The value is normalized to the canonical lowercase form on construction, so
the canonical string, 32 hexadecimal characters, 16 raw bytes and any
`Stringable` returning one of those are all accepted — including
`Ramsey\Uuid\UuidInterface` itself.

`UuidValueBuilder` binds the canonical string, which is correct for
PostgreSQL and MSSQL. It is left non-final with a single `prepareValue()`
seam so drivers storing raw bytes override one method and reuse
`DbUuidHelper::uuidToBlob()`, which until now was unused by `src/`.

No behaviour changes for existing code: passing raw bytes or a raw string
keeps working exactly as before.
@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.62%. Comparing base (11032c2) to head (6795ad2).

Additional details and impacted files
@@            Coverage Diff            @@
##             master    #1199   +/-   ##
=========================================
  Coverage     98.62%   98.62%           
- Complexity     1641     1645    +4     
=========================================
  Files           120      122    +2     
  Lines          4283     4293   +10     
=========================================
+ Hits           4224     4234   +10     
  Misses           59       59           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Tigrov Tigrov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a part of realization, with canonical string form. What about realizations for other concrete DBMS?


public function build(ExpressionInterface $expression, array &$params = []): string
{
return $this->queryBuilder->buildValue($this->prepareValue($expression), $params);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If UUID is binary value, should it be built as binary?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6795ad2. prepareValue() now returns a Param, so DBMS that store a UUID as raw bytes can bind it as DataType::LOB instead of letting buildValue() infer DataType::STRING.

Comment thread src/Expression/Value/UuidValue.php Outdated
Comment on lines +53 to +60
try {
$this->value = strtolower(DbUuidHelper::toUuid((string) $value));
} catch (InvalidArgumentException $e) {
throw new InvalidArgumentException(
'Value is not a valid UUID. Expected the canonical form, 32 hexadecimal characters or 16 raw bytes.',
previous: $e,
);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest to change the message of current exception instead adding one more try-catch.

throw new InvalidArgumentException('Length of source data is should be 16 or 32 bytes.');

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6795ad2. Reworded the exception in DbUuidHelper::toUuid() and dropped the extra try/catch in UuidValue.

Return a `Param` from `UuidValueBuilder::prepareValue()` so DBMS that store
a UUID as raw bytes can bind it as `DataType::LOB` instead of letting
`buildValue()` infer `DataType::STRING` from the PHP type.

Reword the exception in `DbUuidHelper::toUuid()` and drop the extra
try/catch that wrapped it in `UuidValue`.
@KalimeroMK

KalimeroMK commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

@Tigrov correct, this is only the core half — the default builder covers PostgreSQL and MSSQL, while MySQL/MariaDB/SQLite/Oracle override prepareValue() to bind the raw bytes.

Correction to what I wrote earlier about CI: no tag is needed. install-packages resolves yiisoft/db from a same-named branch in my fork, so I can open the driver PRs from add-1152-uuid-value and they will be green against this branch right away. I will do that, so you can review the whole thing as one unit.

KalimeroMK pushed a commit to KalimeroMK/db-sqlite that referenced this pull request Sep 8, 2026
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.
KalimeroMK pushed a commit to KalimeroMK/db-mysql that referenced this pull request Sep 8, 2026
MySQL and MariaDB store a UUID as 16 raw bytes in a `binary(16)` column,
so `prepareValue()` converts the canonical form with
`DbUuidHelper::uuidToBlob()` and binds it as `DataType::LOB`.

Requires yiisoft/db#1199.
KalimeroMK pushed a commit to KalimeroMK/db-oracle that referenced this pull request Sep 8, 2026
Oracle stores a UUID as 16 raw bytes in a `raw(16)` column, so
`prepareValue()` converts the canonical form with
`DbUuidHelper::uuidToBlob()` and binds it as `DataType::LOB`.

Requires yiisoft/db#1199.
@KalimeroMK

KalimeroMK commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

@Tigrov driver PRs are up, green against this branch on real databases:

db-pgsql and db-mssql need nothing.

Oracle could not use the prepareValue() seam: PDO_OCI inserts NULL for a raw(16) column bound as DataType::LOB, so it emits a HEXTORAW() literal instead, as prepareBinary() already does there.

Correction to my earlier note about CI: composer-dependency-analyser does a plain composer install, so it stays red in all three until 2.0.2 is tagged and the drivers bump yiisoft/db to ^2.0.2. I will push that bump when you tag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants