I'm trying to execute a raw query with a prepared statement in PDO-like style using unquoted params (cases 1, 1A), anyway it throws an exception:
An exception occurred while executing 'SELECT * FROM pages WHERE title LIKE :title': You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ':title' at line 1
Also, quoting named param doesn't work (case 2), it doesn't throw an exception but doesn't find anything as well.
Using unnamed/numbered and unquoted params (cases 3, 3A) or executeQuery()
instead of prepare()
(case 4) works as required. Especially that I would like to use named params, the last one is my choice.
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
...
public function queryPagesByTitle(string $title = null): array
{
/** @var Connection $conn */
$conn = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('pages');
// Case 1: DOESN'T work with non-quoted params
$stmt = $conn->prepare("SELECT * FROM pages WHERE title LIKE :title");
$stmt->execute(['title' => $title]);
// Case 1A: DOESN'T work with non-quoted params
$stmt = $conn->prepare("SELECT * FROM pages WHERE title LIKE :title");
$stmt->bindValue('title', $title, \PDO::PARAM_STR);
$stmt->execute();
// Case 1B: DOESN'T work with non-quoted,unique params
$stmt = $conn->prepare("SELECT * FROM pages WHERE title LIKE :dcUniqueParam");
$stmt->bindParam('dcUniqueParam', $title, \PDO::PARAM_STR);
$stmt->execute();
// Case 1C: DOESN'T work with non-quoted,unique params even with :colon while binding
$stmt = $conn->prepare("SELECT * FROM pages WHERE title LIKE :dcUniqueParam");
$stmt->bindParam(':dcUniqueParam', $title, \PDO::PARAM_STR);
// Case 2: DOESN'T work with quoted params neither, doesn't throw an exception, but doesn;t find anything
$stmt = $conn->prepare("SELECT * FROM pages WHERE title LIKE ':title'");
$stmt->execute(['title' => $title]);
// Case 3: Works with numbered param(s)
$stmt = $conn->prepare("SELECT * FROM pages WHERE title LIKE ?");
$stmt->execute([1 => $title]);
// Case 3A: Works with numbered param(s)
$stmt = $conn->prepare("SELECT * FROM pages WHERE title LIKE ?");
$stmt->bindParam(1, $title, \PDO::PARAM_STR);
$stmt->execute();
// Case 4: Works with non-quoted named param(s)
$stmt = $conn->executeQuery(
"SELECT uid, title FROM pages WHERE title LIKE :title",
['title' => $title],
[\PDO::PARAM_STR]
);
return $stmt->fetchAll(FetchMode::ASSOCIATIVE);
}
Several questions
- Why the first case doesn't work as I would expect it after PDO inheritance or how also Doctrine actualy does it?
- Are there some disadvantages (if any) of using
executeQuery()
instead ofprepare()
? - Should I use
prepare()
with numbered params instead? - Is there any significant difference between using raw queries instead of QueryBuilder?
Note
I'm aware that for proper work with models data and repositories I can/should use common QueryBuilder interface. This case is for some raw data in my DB that doesn't use data mapping and I'm looking for some performance improvement. pages
table is used here only for demonstrating the concept.
In the end it all comes down to a PDO-like statement, however with an essential difference between using
mysqli
as driver (https://www.php.net/manual/en/mysqli.quickstart.prepared-statements.php) orpdo_mysql
as driver (https://www.php.net/manual/en/pdo.prepared-statements.php).The important aspect is mentioned in PDO docs (https://www.php.net/manual/en/pdo.prepare.php):
The code snippets given in the original question are working when using
pdo_mysql
as driver - instead ofmysqli
which can be configured intypo3conf/LocalConfiguration.php
Now focussing on the internal details of Doctrine DBAL, which is also just a wrapper for
mysqli
orpdo_mysql
- internally DBAL is using positioned parameters?
and converts named parameters accordingly.Actually that happens in Doctrine DBAL's Connection - named parameters are transformed to positioned parameters (no matter which database driver has been used):
gets converted to
Summary
Connection::executeQuery
usesConnection::prepare
internally, see https://github.com/doctrine/dbal/blob/2.10.x/lib/Doctrine/DBAL/Connection.php#L886?
as well - named parameters:dcValue
are just "virtual"$stmt->bindParam
directly does not perform that transformation process, usingConnection::executeQuery
does and explains why that snipped workedBesides that, since you're in a TYPO3 environment already, you might want to use it's
QueryBuilder
that uses prepared statements internally as well.