Driver detection & dialects
CDO adapts its SQL to the database it’s actually talking to. This page explains how it figures out which database that is, and every place the generated SQL branches on it — written from the connection code, not from memory.
The problem PDO leaves open
PDO::ATTR_DRIVER_NAME returns 'mysql' for both MySQL and MariaDB. But the
two diverge on a feature CDO relies on: MariaDB ≥ 10.5 supports
INSERT ... RETURNING; MySQL never has. Treating them the same would break the
returned-id path on one of them.
How the driver is normalised
At connect time (applyDatabase()), CDO reads PDO::ATTR_DRIVER_NAME and, for
mysql, inspects PDO::ATTR_SERVER_VERSION. If the version string contains
"MariaDB" (e.g. "5.5.5-10.11.6-MariaDB" or "11.4.2-MariaDB"), the driver is
recorded as mariadb. The normalised value — one of pgsql, mysql, mariadb,
oci — is cached and returned by getDriverName().
ATTR_DRIVER_NAME = 'pgsql' → 'pgsql'
ATTR_DRIVER_NAME = 'mysql', version has 'MariaDB' → 'mariadb'
ATTR_DRIVER_NAME = 'mysql', version lacks it → 'mysql'
ATTR_DRIVER_NAME = 'oci' → 'oci'PDO attributes set per driver
applyDatabase() also tunes PDO based on the raw driver:
| Driver | ATTR_EMULATE_PREPARES |
Rationale |
|---|---|---|
pgsql |
false |
Native server-side prepares |
mysql |
true |
Emulated prepares |
oci |
true |
Emulated prepares |
The default fetch mode is set to PDO::FETCH_ASSOC for every driver, so inherited
fetch() / fetchAll() return associative arrays unless you override it.
Where the SQL branches: returned id
insert() uses RETURNING only where it’s supported, and lastInsertId()
elsewhere:
| Driver | insert() strategy |
|---|---|
pgsql |
INSERT … RETURNING pk → fetchColumn() |
mariadb |
INSERT … RETURNING pk → fetchColumn() |
mysql |
plain INSERT → lastInsertId() |
oci |
plain INSERT → lastInsertId() |
A falsy result is normalised to null — meaning “no generated id to report”
(e.g. an INSERT into a table without an auto-increment/serial column), not an
error. Real SQL errors come through PDOException and become CDOException.
Upsert RETURNING is narrower
upsert() uses RETURNING on PostgreSQL only, even though MariaDB supports
RETURNING for plain inserts — MariaDB disallows it together with
ON DUPLICATE KEY UPDATE / INSERT IGNORE, so CDO falls back to lastInsertId()
there. See Upsert placeholders.
Where the SQL branches: upsert dialect
| Driver | Ignore conflicts | Update on conflict |
|---|---|---|
pgsql |
ON CONFLICT (cols) DO NOTHING |
ON CONFLICT (cols) DO UPDATE SET … |
| everything else | INSERT IGNORE |
ON DUPLICATE KEY UPDATE … |
The :new / :current token substitution also depends on the driver — full
matrix in Upsert placeholders.
Where the SQL branches: timezone
On connect, CDO sets the database session timezone to PHP’s
(date_default_timezone_get()), per driver:
| Driver | Statement |
|---|---|
pgsql |
SET TIMEZONE TO '<tz>' |
mysql (and MariaDB) |
SET time_zone = '<offset>' |
oci |
ALTER SESSION SET TIME_ZONE = '<tz>' |
| other | not implemented — logs a warning |
MySQL path needs an offset helper
For MySQL/MariaDB, CDO converts the PHP timezone name to a numeric offset via a
timezoneToOffset() function it expects to exist in the global scope. It is not
shipped by this package — it’s provided by the host Winter framework. Outside that
framework, define an equivalent global (or the MySQL SET time_zone step is
skipped when the helper returns null). PostgreSQL and Oracle take the timezone
name directly and have no such dependency.
Driver capability summary
| Operation | PostgreSQL | MySQL | MariaDB | Oracle |
|---|---|---|---|---|
insert returns id |
✅ RETURNING | ✅ lastInsertId | ✅ RETURNING | ⚠️ lastInsertId |
insertGroup |
✅ | ✅ | ✅ | ✅ |
upsert / upsertGroup |
✅ | ✅ | ✅ | ❌ |
update / delete |
✅ | ✅ | ✅ | ✅ |
Related
- Upsert placeholders — token substitution per driver
- CDO API —
getDriverName() - Batch & chunking — how multi-row SQL is assembled