CDO API
CDO extends PDO, adding the DML methods below. Everything else — query,
prepare, fetch*, transactions, attributes — is inherited from PDO unchanged.
Every method here throws CDOException on failure,
wrapping the original PDOException.
Identifiers are quoted; values are bound
Values are bound as prepared-statement parameters. Table and column names — which
cannot be bound — are quoted with the driver’s quoting characters ("…" for
PostgreSQL/SQLite/Oracle, `…` for MySQL/MariaDB), so these DML methods are safe
even with dynamically built names. Because quoted identifiers are
case-sensitive in PostgreSQL, pass names exactly as they exist in the schema
(users, not Users). Note this quoting applies to the CDO methods only — column
names passed to Qb are not quoted.
Construction
You rarely construct CDO directly — ConnectionPool
does it for you. The constructor is:
public function __construct(
DbConfigInterface $config,
int $timeout = 5,
bool $debug = false
)| Parameter | Type | Default | Description |
|---|---|---|---|
config |
DbConfigInterface |
— | Connection config (credentials, driver, logger) |
timeout |
int |
5 |
Connection timeout in seconds (PDO::ATTR_TIMEOUT) |
debug |
bool |
false |
When true, sets PDO::ERRMODE_EXCEPTION |
getDriverName
public function getDriverName(): stringReturns the normalised driver: 'pgsql', 'mysql', 'mariadb', 'sqlite', or 'oci'.
Unlike PDO::ATTR_DRIVER_NAME, this distinguishes MariaDB from MySQL — see
Driver detection.
insert
final public function insert(string $table, object|array $entity): mixedInserts one row and returns the generated primary key.
| Parameter | Type | Description |
|---|---|---|
table |
string |
Table name |
entity |
`object\ | array` |
- Returns the primary key — the value of the entity’s first key, read via
RETURNING(PostgreSQL/MariaDB) orlastInsertId()(MySQL/SQLite/Oracle). Returnsnullwhen there is no generated id to report. nullvalues inentityare excluded from theINSERT.- Throws
CDOExceptionon query failure.
insertBatch
final public function insertBatch(
string $table,
iterable $entities,
int $chunkSize = 1000
): intBatch-inserts many rows as chunked multi-row INSERTs.
| Parameter | Type | Default | Description |
|---|---|---|---|
table |
string |
— | Table name |
entities |
iterable |
— | Array or generator of arrays/objects |
chunkSize |
int |
1000 |
Rows per INSERT statement |
- Returns the total number of inserted rows — a plain
INSERTreports this count identically on PostgreSQL/MySQL/MariaDB/SQLite. An emptyentitiesarray is a no-op that returns0. - Rows are grouped by their column signature (the set of non-null columns)
before chunking, so rows sharing the same shape batch together and mixed
shapes no longer break the multi-row
VALUESlist. - Per-row
nullvalues are excluded. ThrowsCDOExceptionon failure, or if a row has no non-null columns.
upsert
final public function upsert(
string $table,
object|array $entity,
array $conflictColumns,
?array $updateColumns = null
): mixedInserts a row, or updates/ignores it on unique conflict.
| Parameter | Type | Default | Description |
|---|---|---|---|
table |
string |
— | Table name |
entity |
`object\ | array` | — |
conflictColumns |
array |
— | Column(s) defining uniqueness |
updateColumns |
`array\ | null` | null |
- Returns the primary key on PostgreSQL (via
RETURNING); on MySQL/MariaDB/SQLite returnslastInsertId()(nullif no new row was inserted). - Conflict syntax adapts to the driver: PostgreSQL and SQLite use
ON CONFLICT, while MySQL/MariaDB useINSERT IGNORE/ON DUPLICATE KEY UPDATE. - Throws
CDOExceptionifconflictColumnsis empty, or on query failure. - Expression tokens
:new/:current— see Upsert placeholders.
upsertBatch
final public function upsertBatch(
string $table,
iterable $entities,
array $conflictColumns,
?array $updateColumns = null,
int $chunkSize = 500
): voidBatch upsert with chunking.
| Parameter | Type | Default | Description |
|---|---|---|---|
table |
string |
— | Table name |
entities |
iterable |
— | Array or generator of arrays/objects |
conflictColumns |
array |
— | Column(s) defining uniqueness |
updateColumns |
`array\ | null` | null |
chunkSize |
int |
500 |
Rows per statement |
- Returns nothing — void by design: an upsert’s affected-row count is not
comparable across drivers, so there is no stable value to return. Empty
entities⇒ no-op; emptyconflictColumns⇒ throws.
update
final public function update(string $table, object|array $entity, Qb $qb): intUpdates rows matching a Qb condition.
| Parameter | Type | Description |
|---|---|---|
table |
string |
Table name |
entity |
`object\ | array` |
qb |
Qb |
Condition for the WHERE clause |
- Returns the number of affected rows (
rowCount()). - Throws
CDOExceptionon failure.
delete
final public function delete(string $table, Qb $qb): intDeletes rows matching a Qb condition.
| Parameter | Type | Description |
|---|---|---|
table |
string |
Table name |
qb |
Qb |
Condition for the WHERE clause |
- Returns the number of deleted rows. Throws
CDOExceptionon failure.
transaction
public function transaction(Closure $callback): voidRuns $callback inside a transaction: begins, invokes the callback, and commits
on success. If the callback throws, the transaction is rolled back and the
exception re-thrown.
- The rollback is guarded by
inTransaction()and its own failure is logged (not thrown), so the original callback exception is always the one that propagates — a failing rollback never masks it. - Throws whatever the callback throws (after the rollback attempt).
Cross-database behaviour
Works on all drivers, but with caveats: on MySQL only InnoDB tables roll back (MyISAM does not). DDL inside a transaction is transactional on PostgreSQL/SQLite, but on MySQL/MariaDB/Oracle a DDL statement causes an implicit commit and cannot be rolled back. Nesting is not supported — PDO does not support nested transactions.
applyDatabaseTimezone
public function applyDatabaseTimezone(mixed $driver, string $tz): voidSets the session timezone to match PHP’s. Called automatically at connect time; exposed as public so you can re-apply it after a driver-level reset. Behaviour per driver is documented in Driver detection.
Writes only; reads stay PDO
There is intentionally no select / find here. Read with the inherited PDO
methods and a Qb fragment for the WHERE.
Related
- Qb operators — building the
WHEREconditions - Configuration — configs, pool, statement, bind
- Exceptions — failure model and SQLSTATE codes