It's quite simply shoddy string substitution that's not doing proper escaping, as you pointed out.
It's quite simply shoddy string substitution that's not doing proper escaping, as you pointed out.
No amount of prepared statement kung-fu will save you when the querystring itself contains untrustworthy data. Which is exactly what Drupal is doing here. It puts untrusted, potentially non-integer array keys directly into a querystring. Even if Drupal used a database library that supported proper prepared statements, it would have been owned just as well, only slightly less severely.
It's similar to another, much more common misuse of prepared statements: using untrusted values in the column name.
Still, it's always a good idea to set PDO::ATTR_EMULATE_PREPARES to FALSE as soon as you create any instance of that bloody class.
Something between PHP and MSSQL must not support named parameters, because MSSQL supports them just fine.