Skip to content

Use VarString for string parameter encoding in binary protocol#142

Open
webpatser wants to merge 4 commits intoamphp:3.xfrom
webpatser:fix/string-parameter-type-for-mariadb-uuid
Open

Use VarString for string parameter encoding in binary protocol#142
webpatser wants to merge 4 commits intoamphp:3.xfrom
webpatser:fix/string-parameter-type-for-mariadb-uuid

Conversation

@webpatser
Copy link
Copy Markdown
Contributor

Summary

  • String parameters in prepared statements are currently encoded as MYSQL_TYPE_LONG_BLOB (0xfb), which tells the server to treat values as raw binary data
  • This breaks MariaDB's native UUID column type (introduced in MariaDB 10.7), which requires string-typed parameters to trigger string→UUID parsing
  • Changes the type to MYSQL_TYPE_VAR_STRING (0xfd), which is more semantically correct for PHP string values and matches MySQL's own C API behavior for string binds

Why this matters

MariaDB 10.7+ introduced a native UUID column type that stores UUIDs as 16-byte binary internally. When a prepared statement parameter is typed as LONG_BLOB, MariaDB interprets the 36-byte ASCII UUID string as raw bytes instead of parsing the human-readable format. With VAR_STRING, MariaDB correctly converts the string representation to its internal format.

Safety

The wire encoding (length-prefixed bytes) is identical for both LongBlob and VarString — only the 2-byte type header in COM_STMT_EXECUTE changes. All existing tests pass (136/136).

This is effectively a no-op for all standard MySQL/MariaDB column types (VARCHAR, CHAR, TEXT, BLOB, etc.) since both types trigger the same implicit conversion paths. The difference only surfaces for MariaDB-specific types like UUID that distinguish between string and binary parameter sources.

Suggestion

The current test suite only runs against MySQL. Adding MariaDB to the CI matrix (related: #80) would help catch driver-specific issues like this. Happy to help with that in a follow-up PR if there's interest.

String parameters in prepared statements were encoded with
MYSQL_TYPE_LONG_BLOB (0xfb), which tells the server to treat
the value as raw binary data. This breaks MariaDB's native UUID
column type (introduced in 10.7), which requires the parameter
to be declared as a string type to trigger string-to-UUID parsing.

Changing to MYSQL_TYPE_VAR_STRING (0xfd) is more semantically
correct for PHP string values and matches what MySQL's own C API
uses for string binds. The wire encoding (length-prefixed bytes)
is identical for both types, so this is a no-op for all standard
column types on both MySQL and MariaDB.
Psalm 6.15.1 on PHP 8.4.19 incorrectly reports InvalidAttribute
for #[\Override] attributes. This was green on PHP 8.4.18 but
broke with the runner update. Suppress until Psalm is updated.
@webpatser
Copy link
Copy Markdown
Contributor Author

Note: The CI failures on PHP 8.2–8.5 are unrelated to this change — Psalm 6.15.1 started reporting InvalidAttribute for #[\Override] after the GitHub runner updated to PHP 8.4.19 (8.4.18 was green). The actual test suite passes on all PHP versions.

webpatser added a commit to webpatser/fledge-fiber that referenced this pull request Apr 10, 2026
Security:
- Fix HTTP/2 ping flood on active streams (amphp/http-server#386)

Bug fixes:
- Use VarString for string params in binary protocol (amphp/mysql#142)
- Decode BIT columns as int instead of string (amphp/mysql#138)
- Close connections on pool destruct (amphp/http-client#396)
- Fix duplicate keys in byte-stream split() (amphp/byte-stream#113)
- Fix Closure type annotation for static analysis (amphp/amp#451)
- Safely handle DisposedException on unsubscribe (amphp/redis#100)

Features:
- Add TLS support for Redis connections (amphp/redis#98)
- Add disperse() for concurrent closure execution (amphp/amp#460)
@trowski
Copy link
Copy Markdown
Member

trowski commented May 1, 2026

I wonder if this could negatively affect a prepared statement providing data for a blob-type field via a prepared statement.

I think this makes sense for string types by default, but I think this might need to be coupled with changes to the connection processor to set the value type based on the column type.

Switching every string parameter to VarString broke binary payloads sent
to BLOB columns: VarString carries connection-charset semantics, while
LongBlob is treated as binary (charset 63). Non-UTF8 bytes flowing into
a BLOB column on a utf8mb4 connection could trip "Incorrect string
value" or transcode silently.

The prepare response already exposes the target type per parameter, so
ConnectionProcessor::execute now forwards it to MysqlEncodedValue. For
PHP string values, the encoder picks LongBlob when the target is in the
Blob family (TinyBlob, Blob, MediumBlob, LongBlob) and VarString
otherwise. The same branch is applied to the prebound types entry,
which previously hardcoded VarString and would have hit the same issue
for COM_STMT_SEND_LONG_DATA writes to blob columns.

Keeps the MariaDB UUID fix intact (string targets like Varchar, String,
VarString fall through to VarString) while leaving binary blobs binary.
@webpatser
Copy link
Copy Markdown
Contributor Author

Hey @trowski, fair point and you're right. I'd been telling myself "wire format is identical, so no behavior change", but VarString carries connection-charset semantics where LongBlob is binary (charset 63), so a non-UTF8 payload (raw bytes, gzip, encrypted) headed into a BLOB column could trip Incorrect string value on a utf8mb4 connection or transcode silently. That's not a no-op.

Pushed b603de7 with the column-type-aware version:

  • MysqlEncodedValue::fromValue() now takes an optional target MysqlDataType
  • for string PHP values it returns LongBlob when the target is TinyBlob / Blob / MediumBlob / LongBlob, otherwise VarString
  • the prebound types entry in ConnectionProcessor::execute uses the same branch (it was hardcoded to VarString before, so this also fixes long-data sends to blob columns)
  • Stringable / BackedEnum recurse with the target type preserved

Added unit coverage in test/MysqlEncodedValueTest.php (17 assertions) covering the type-selection table, binary random_bytes into Blob, Stringable into LongBlob, and the non-string PHP types staying unchanged.

The MariaDB UUID fix still works since String / Varchar / VarString all fall through to VarString.

Adding MariaDB to the CI matrix in a follow-up still makes sense since the UUID side of this only exercises on MariaDB. Happy to take that on after this lands.

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants