DBIO

 view release on metacpan or  search on metacpan

share/skills/dbio-driver-development/SKILL.md  view on Meta::CPAN

__PACKAGE__->_use_multicolumn_in(1);

# Tier 2: Detect at runtime (only if Tier 1 undef)
sub _determine_supports_insert_returning {
  return shift->_server_info->{normalized_dbms_version} >= 8.002 ? 1 : 0;
}
```

Result cached in `_supports_*` (computed once).

```perl
# SQLite: multicolumn IN since 3.14
sub _determine_supports_multicolumn_in {
  ( shift->_server_info->{normalized_dbms_version} < '3.014' ) ? 0 : 1
}
```

## Key Storage Methods

| Method | Purpose | Override? |
|--------|---------|-----------|
| `register_driver()` | auto-detect registry | yes (at load) |
| `_rebless()` | post-detect init hook | optional |
| `last_insert_id()` | autoincrement | usually |
| `sqlt_type()` | SQL::Translator name | yes |
| `_exec_svp_begin/_release/_rollback()` | savepoints | if DB supports |
| `with_deferred_fk_checks()` | defer FK | if DB supports |
| `connect_call_*()` | connect-time setup | optional |
| `bind_attribute_by_data_type()` | DBI bind per type | optional |
| `datetime_parser_type` | DateTime parser | class data |
| `sql_quote_char` | identifier quote | class data |
| `sql_maker_class` | custom SQLMaker | class data |
| `cake_defaults()` | Cake flags (`-Pg` etc) | optional |

### cake_defaults()

Optional. Driver flags for `DBIO::Cake`. Activated by `use DBIO::Cake '-Pg'`.

```perl
sub cake_defaults {
  return (
    inflate_jsonb     => 1,   # jsonb only (leaves json() free)
    inflate_datetime  => 1,
    retrieve_defaults => 1,   # PG generates UUIDs, serials, NOW()
  );
}
```

Cake looks up via `DBIO::Storage::DBI->driver_storage_class($name)`.

Inherited for free: connection/disconnection, SQL gen via SQLMaker, txn_*, insert/update/delete/select, handle caching, prepared statements, DBH attrs.

## AccessBroker

All drivers support AccessBroker. Pass broker to `Schema->connect($broker)` instead of raw DSN. **Full broker interface lives in dbio-core skill.**

Storage detects via `_is_access_broker_connect_info([$broker])` (true if single blessed). Then:
1. `set_access_broker($broker, 'write')` attaches
2. `_current_dbi_connect_info($mode)` → `current_access_broker_connect_info($mode)`
3. Broker returns HASHREF, Storage normalizes
4. Connection proceeds with broker credentials

Rotating creds: storage re-fetches on next connect. Async pools refresh via `_conninfo_provider` calling `current_connect_info_for_storage($storage, $mode)`.

## Async Drivers (ADR 0030/0031)

Async is an explicit, **per-connection mode** (ADR 0030), not a separate storage
class chosen at schema-author time. A schema connected with
`connect(..., { async => $mode })` answers the six `*_async` storage methods and
the ResultSet/Row `*_async` helpers through an embedded async backend; without
`{ async => ... }` it stays sync (`*_async` croaks — no auto-fallback). An add-on
registers its mode on the core base storage:

    DBIO::Storage::DBI->register_async_mode( $mode => 'DBIO::SomeDriver::Backend::Storage' );

`forked` (and the core `immediate`) register generically on the base class; a
native `ev` mode is registered by the concrete *sync* driver storage, so
`{ async => 'ev' }` resolves DB-specifically. `future_io` is **not registered** —
the core resolver discovers each driver's transport adapter by convention,
`ref($storage) . '::Async'` (`DBIO::X::Storage` → `DBIO::X::Storage::Async`), and
croaks early if a driver ships none (ADR 0030 refinement, karr #65).

### Extensions are storage LAYERS, not storage_type subclasses (karr #70)

A driver **extension** (AGE, PostGIS, a tenant add-on) does **not** subclass
`storage_type` and does **not** ship a per-extension `<pkg>::Async` transport of
its own. Under the storage-layer composition model it ships a plain storage
**layer** — a method package, no `@ISA` pointing at a storage — registered on
the schema:

    $schema->register_storage_layer('DBIO::X::Ext::Storage');

Core composes every registered layer over the base storage by C3-MRO class
synthesis (`DBIO::Storage::Composed->compose($base, \@layers)`): the synthesised
`DBIO::Storage::Composed::<Layer>__<Base>` has `@ISA = (@layers, $base)` under
the `c3` MRO and no methods of its own, so calls walk the layers (registration
order = precedence) and fall through to the base; layer hooks chain with
`$self->next::method(@_)`. Two layers defining the *same own method* is a compose-
time croak — silent shadowing between siblings is forbidden. The driver rebless
(`_determine_driver`) re-composes the same layers over the concrete driver class
(`recompose`), so a layer chosen on the generic storage survives onto the driver.

**Async rides the same layers.** When a layered schema connects
`{ async => $mode }`, core (1) resolves the transport off the composition
**BASE** — the driver, never the layers (`_async_resolution_class` strips the
layers back out of the linearised ISA, so a sync layer's plain `<Layer>::Async`
mixin is never mistaken for the transport, karr #70/#67) — then (2) mirrors each
registered sync layer `L` onto its async counterpart (`L->async_layer_class($mode)`
if defined, else the convention sibling `${L}::Async` via `load_optional_class`;
absent ⇒ that layer is sync-only and skipped) and composes those mirrors **on top
of** the transport. One behaviour, one transport: an extension's async behaviour
rides every transport exactly as its sync behaviour rides every driver, with no
hand-written extension×transport matrix. An async mirror that declares
`required_transport_capabilities` the transport does not advertise croaks at
compose time naming the gap (see the capability table below).

| Aspect | DBI | Async |
|--------|-----|-------|
| Base | `DBIO::Storage::DBI` | `DBIO::Storage::Async` |
| Protocol | DBD | native (EV::Pg/libpq, EV::MariaDB) |
| Returns | blocking | Future |



( run in 0.795 second using v1.01-cache-2.11-cpan-f52f0507bed )