Problem/Motivation
The PostgreSQL driver casts PHP booleans to integers before binding query arguments:
// The PDO PostgreSQL driver has a bug which doesn't type cast booleans // correctly when parameters are bound using associative arrays. // @see http://bugs.php.net/bug.php?id=48383 foreach ($args as &$value) { if (is_bool($value)) { $value = (int) $value; } }
core/modules/pgsql/src/Driver/Database/pgsql/Connection.php, checked on 11.4.4.
There is no equivalent cast in the SQLite driver, the MySQL driver, or the shared database layer. grep -rn 'is_bool' core/modules/sqlite core/modules/mysql core/lib/Drupal/Core/Database returns a single unrelated hit in Database.php.
So the same API call behaves differently depending on the database:
- PostgreSQL — the cast above turns
FALSEinto0. Matches. - MySQL — binds as
'', which MySQL coerces to0. Matches. - SQLite — binds as
'', and SQLite's type affinity does not coerce it. Matches nothing.
The failure is silent — it returns a wrong answer rather than an error, and only on one supported database. Any module writing $query->condition($boolean_field, FALSE), which reads as entirely natural, has this bug on SQLite today.
A concrete case, because the shape generalises: a contributed module's cron reaper found expired records with $query->condition('revoked', FALSE). On SQLite it matched nothing, so time-boxed privileges were never revoked — and nothing errored. It shipped that way for months and was found by reading the code, not by anyone noticing. MySQL and PostgreSQL hid it completely.
Steps to reproduce
PDO level, PHP 8.4, SQLite in memory:
$pdo->exec('CREATE TABLE grants (id INTEGER PRIMARY KEY, revoked INTEGER NOT NULL DEFAULT 0)'); $pdo->exec('INSERT INTO grants (id, revoked) VALUES (1, 0)'); // then, for each value: SELECT COUNT(*) FROM grants WHERE revoked = :v
Result:
false (PHP bool) -> 0 rows int 0 -> 1 rows string '0' -> 1 rows empty string '' -> 0 rows PHP false binds as typeof=text quote=''
That last line is the cause: PDO binds a PHP false as the empty string, and SQLite evaluates '' = 0 as false.
Proposed resolution
Move the cast out of the pgsql driver and into the shared layer, so every driver agrees rather than one driver compensating for a PDO quirk the others share.
Failing that, give the SQLite driver the same cast pgsql already has — though that leaves the next driver to rediscover it.
Remaining tasks
- Confirm the preferred layer for the cast (shared vs per-driver).
- Add test coverage asserting a boolean condition matches identically across all three drivers.
- Check whether the entity query layer needs anything beyond the database-layer fix.
User interface changes
None.
API changes
None intended. Queries that already work on MySQL and PostgreSQL would begin working on SQLite; a query relying on the current SQLite behaviour would be relying on matching nothing.
Data model changes
None.
Release notes snippet
Passing a PHP boolean as a query condition value now matches consistently across the MySQL, PostgreSQL and SQLite drivers. Previously only the PostgreSQL driver cast booleans to integers, so a condition comparing a field to FALSE silently matched no rows on SQLite.
Comments
Comment #2
jmcerda commentedComment #3
jmcerda commentedComment #4
daffie commentedIs now being fixed in #3343634: Add "json" as core data type to Schema and Database API.
Comment #5
jmcerda commented