-
Notifications
You must be signed in to change notification settings - Fork 0
Drivers
A driver is responsible for identifier escaping — quoting table / column names, alias references, dotted paths — and reporting its canonical name. Nothing else; the dialect-specific clause grammar lives in the compilers.
| Driver string | Class | Escape char | Notes |
|---|---|---|---|
'mysql' |
Drivers\MySqlDriver |
` |
|
'pgsql' / 'postgres' / 'postgresql'
|
Drivers\PostgreSqlDriver |
" |
|
'sqlite' |
Drivers\SqliteDriver |
` |
Same as MySQL — SQLite accepts both. |
null or any unknown string |
Drivers\GenericDriver |
(none) | No identifier quoting — pass-through. |
use InitORM\QueryBuilder\QueryBuilder;
new QueryBuilder('mysql'); // → MySqlDriver
new QueryBuilder('pgsql'); // → PostgreSqlDriver
new QueryBuilder('sqlite'); // → SqliteDriver
new QueryBuilder(); // → GenericDriver
new QueryBuilder('unknown'); // → GenericDriverinterface DriverInterface
{
public function escapeIdentifier(string $identifier): string;
public function getName(): ?string;
}-
escapeIdentifier()is pure — the input is returned (possibly modified) but never mutated. -
getName()returns the canonical lowercase name, ornullfor drivers that don't apply a dialect.
The implementation in AbstractDriver::escapeIdentifier():
-
Identifier-shaped tokens (
[a-zA-Z_][a-zA-Z0-9_]*) get wrapped with the escape char. -
Bind-parameter prefixes (
:foo) are skipped —:foostays:foo, never:`foo`. - SQL keywords AND, OR, AS, ON (both cases) are skipped.
- Pre-existing escape characters inside the identifier are doubled (the standard SQL "escape the escape" rule).
-
Query-breakout characters
;and--raiseQueryBuilderInvalidArgumentException(v2.0.0 hardening — see Security §V3).
Examples (using MySqlDriver, with ` as the escape char):
$d = new MySqlDriver();
$d->escapeIdentifier('id'); // `id`
$d->escapeIdentifier('users.id'); // `users`.`id`
$d->escapeIdentifier('users AS u'); // `users` AS `u`
$d->escapeIdentifier('a.id AND b.id'); // `a`.`id` AND `b`.`id`
$d->escapeIdentifier(':bind_value'); // :bind_value (untouched)
$d->escapeIdentifier('weird`name'); // `weird``name` (escape doubled)
$d->escapeIdentifier('users; DROP'); // throws QueryBuilderInvalidArgumentExceptionNumeric literals (digit-leading tokens) are NOT matched by the regex —
they pass through unquoted. That's intentional — string fragments like
x = 1 OR y = 2 only quote the identifiers:
$d->escapeIdentifier('x=1 OR y=2'); // `x`=1 OR `y`=2Every public clause builder that takes an identifier-shaped argument
runs it through escapeIdentifier() before storing it in the structure.
By the time the structure is compiled, every identifier is already
quoted; the compilers do no quoting themselves.
$qb = new QueryBuilder('mysql');
$qb->from('users AS u')->where('u.country', 'TR');
$qb->exportQB()['table'];
// [ '`users` AS `u`' ]For projections that wrap a column with a function call (COUNT(...),
MAX(...), …), only the column argument is escaped — the function
keyword stays unquoted:
$qb->selectMax('age');
$qb->exportQB()['select'];
// [ 'MAX(`age`)' ]The same query, four ways:
$build = function (?string $dialect) {
$qb = new QueryBuilder($dialect);
return $qb->select('u.id', 'u.name')
->from('users AS u')
->where('u.country', 'TR')
->generateSelectQuery();
};
$build(null);
// SELECT u.id, u.name FROM users AS u WHERE u.country = :u_country
$build('mysql');
// SELECT `u`.`id`, `u`.`name` FROM `users` AS `u` WHERE `u`.`country` = :u_country
$build('pgsql');
// SELECT "u"."id", "u"."name" FROM "users" AS "u" WHERE "u"."country" = :u_country
$build('sqlite');
// SELECT `u`.`id`, `u`.`name` FROM `users` AS `u` WHERE `u`.`country` = :u_countryExtend AbstractDriver and override the two class constants:
namespace App\Db;
use InitORM\QueryBuilder\Drivers\AbstractDriver;
final class OracleDriver extends AbstractDriver
{
protected const NAME = 'oracle';
protected const ESCAPE_CHAR = '"';
}Use it by composing a QueryBuilder with your driver directly, or by
extending QueryBuilder to recognize a new driver string:
namespace App\Db;
use InitORM\QueryBuilder\QueryBuilder as BaseBuilder;
final class QueryBuilder extends BaseBuilder
{
public function __construct(?string $driver = null)
{
parent::__construct($driver);
if ($driver === 'oracle') {
$this->driver = new OracleDriver();
}
}
}Override escapeIdentifier() directly if your dialect needs more than a
single escape character. Below: a hypothetical SQL Server style driver
using square brackets:
final class SqlServerDriver extends AbstractDriver
{
protected const NAME = 'sqlsrv';
protected const ESCAPE_CHAR = ''; // disable the default
public function escapeIdentifier(string $identifier): string
{
return preg_replace(
'/\b(?<!:)(?!(AND|and|OR|or|AS|as|ON|on)\b)([a-zA-Z_][a-zA-Z0-9_]*)\b/',
'[$0]',
$identifier,
);
}
}escapeIdentifier('users.id') now returns [users].[id] and the rest
of the clause builders fall in line automatically.
QueryBuilder::getDriver() returns the live DriverInterface instance —
handy when you want to escape an identifier yourself in a raw fragment:
$col = $qb->getDriver()->escapeIdentifier('users.id');
$qb->where($qb->raw($col . ' = ' . $qb->raw('NOW()')));getDriver()->getName() exposes the driver's name (or null for the
generic driver) and is what newBuilder() uses to propagate the dialect
to a sibling builder.
Next: Security
InitORM QueryBuilder — MIT licensed · authored by Muhammet ŞAFAK · part of the InitORM family · report an issue · security disclosure