NAME

DBIO::PostgreSQL::EV::Storage - Async PostgreSQL storage driver using EV::Pg

VERSION

version 0.900001

DESCRIPTION

The native ev async transport for PostgreSQL (core ADR 0030 / 0031): a thin transport over EV::Pg, a non-blocking libpq client that speaks the async wire protocol directly. No DBI, no DBD::Pg, just raw libpq performance.

It resolves as the ev async mode of DBIO::PostgreSQL::Storage (registered there via register_async_mode( ev => ... )), so a schema connected with { async => 'ev' } answers the six *_async storage methods over EV::Pg while the same schema keeps serving synchronous queries over DBI/DBD::Pg.

Thin transport: shared machinery is inherited

The Model-B orchestration (ADR 0030 §4) is inherited unchanged from DBIO::Storage::Async: the CRUD runner ("_run_crud" in DBIO::Storage::Async with its pooled / pinned runners), the INSERT returned-columns mapping, "txn_do_async" in DBIO::Storage::Async bracketing, the "pipeline" in DBIO::Storage::Async scaffold, the sql_maker plumbing and the sync ->get fallbacks. This class fills only the loop-/wire-specific seams:

Because the extension-composition point is the core resolver, an async storage layer composed onto this transport (core #70) inherits every CRUD/txn path for free; the transport only has to advertise the wire capabilities it really provides (see "transport_capabilities").

Transport value-add

Beyond the CRUD/txn contract this transport carries the PostgreSQL wire features the abstract future_io transport cannot: LISTEN/NOTIFY ("listen" / "notify"), COPY ("copy_in") and libpq pipelining. These override the base's croaking defaults and are declared in "transport_capabilities" so a layer that requires them composes cleanly (and core croaks loudly, never silently dropping the feature, when it cannot).

METHODS

sql_maker_class

The DBIO::PostgreSQL::SQLMaker subclass used to generate SQL. Shared with the sync driver and the future_io transport, so the maker keeps emitting ? placeholders (translated to $N internally by "_transform_sql").

_sql_maker_args

PostgreSQL SQLMaker construction args: double-quote identifier quoting, . name separator, LIMIT/OFFSET dialect. Matches the sync driver and the future_io transport.

_post_insert_sql

Returns RETURNING * so an INSERT yields every populated column (autoinc PK + retrieve-on-insert defaults); the inherited runner folds the row onto the supplied data to build the returned-columns hashref (ADR 0031 §3).

future_class

Returns 'Future' -- uses Future.pm from CPAN. Plain "Future" in Future ->then callbacks auto-wrap a non-Future return value into a resolved Future (ADR 0031 §4), so ResultSet/Row *_async callbacks that return plain values resolve without an explicit Future->done(...) wrap.

transport_capabilities

my @caps = DBIO::PostgreSQL::EV::Storage->transport_capabilities;

Class method (see "transport_capabilities" in DBIO::Storage::Async). Declares the wire capabilities this transport really provides, so "_async_storage" in DBIO::Storage::DBI lets an async extension layer that requires any of them compose onto it (and croaks naming the gap otherwise, rather than silently dropping a feature). This transport provides:

connect_info

$storage->connect_info([ \%conninfo, \%opts ]);

Set connection parameters. %conninfo is passed to EV::Pg as libpq connection parameters (host, dbname, user, ...). When the embedding sync storage feeds its own DBI-form connect info (['dbi:Pg:...', $user, $pass, \%attrs]) it is translated into the async [ \%conninfo, \%opts ] shape first. AccessBroker connect info ([ $broker ]) is detected and wired to the per-spawn credential provider (the base broker seam), exactly as the sync path does.

This override (rather than the inherited base connect_info) is retained because the EV transport (a) folds the DBI-form DSN into libpq conninfo inline and (b) also tears down the dedicated LISTEN connection on reconnect.

_async_broker_conninfo

my $conninfo = $storage->_async_broker_conninfo($mode);

AccessBroker seam (see "ACCESSBROKER CONSUMPTION" in DBIO::Storage::Async): return one fresh, storage-native libpq conninfo hash for a single new pool connection, built from the current broker credentials via the inherited normalisation.

pool

Returns the DBIO::PostgreSQL::EV::Pool connection pool, created lazily on first access. The pool is wired with storage => $self so its core-shared spawn path ("_spawn_connection" in DBIO::Storage::PoolBase) replays the owning sync storage's connect actions on each new connection via "_run_pool_connect_statement" (karr #68). Fed the per-spawn conninfo_provider when an AccessBroker is attached, otherwise the static conninfo string.

_txn_context_class

The pinned-connection transaction context the inherited "txn_do_async" in DBIO::Storage::Async hands to its coderef: DBIO::PostgreSQL::EV::TransactionContext.

_txn_conn_accessor

The constructor key the pinned connection is passed under -- pg, matching DBIO::PostgreSQL::EV::TransactionContext.

_transform_sql

Transport-internal SQL shaping (core #70 / ADR 0032). Rewrite ? placeholders to PostgreSQL positional $N, skipping quoted literals / identifiers and the JSONB @? operator. Called first by "_query_async" / "_query_async_pinned" on the raw sql_maker output; idempotent on already-$N SQL (no bare ? to touch), so a $N passthrough stays intact.

_query_async

Transport seam. Execute a query on a freshly-acquired pooled connection, releasing it once the Future is ready. Returns a Future of the raw result rows (list of column arrayrefs for a result set, or the affected-row count for plain DML -- ADR 0031 §3), exactly what the inherited _run_crud expects.

Receives SQL in the sql_maker ?-placeholder dialect and shapes it into PostgreSQL's positional $N internally (via "_transform_sql") before it reaches libpq -- the core #70 / ADR 0032 seam contract.

_query_async_pinned

Transport seam. Like "_query_async" but runs on the supplied pinned connection and does not release it -- used for queries inside a pinned transaction. Shapes the incoming ?-dialect SQL internally via "_transform_sql" exactly as "_query_async" does.

_run_pool_connect_statement

$storage->_run_pool_connect_statement($conn, $sql, $attrs, @bind);

Native-backend override of the core connect-action runner seam ("_run_pool_connect_statement" in DBIO::Storage::Async). The base default drives a blocking DBI do and croaks on a non-DBI connection; an EV::Pg handle is neither a { dbh => $dbh } wrapper nor a do-capable DBI handle, so this override drives the statement synchronously to completion on that very connection over the EV loop.

Called once per freshly-spawned pool connection from "_setup_pool_connection" in DBIO::Storage::Async, so the owning sync storage's on_connect_do / on_connect_call (including connect_call_* methods such as an extension's connect_call_load_age) replay on every new EV pool connection, identical to the future_io pool. The freshly-spawned EV::Pg handle may still be mid-connect, so we first drive the loop until it is up, then run the statement and drive the loop until it completes. $attrs (the DBI attribute hashref) has no EV::Pg analogue and is ignored.

_pipeline_enter

Open libpq pipeline mode on the connection.

_pipeline_sync

Flush the batched queries and resolve once the pipeline sync point has been acknowledged. EV::Pg's pipeline_sync takes a completion callback; we adapt it to the Future the inherited scaffold expects.

_pipeline_exit

Close libpq pipeline mode on the connection.

listen

$storage->listen($channel, sub {
    my ($channel, $payload, $sender_pid) = @_;
    # Handle notification
});

Subscribe to PostgreSQL LISTEN/NOTIFY notifications on the given channel. The callback fires each time a notification arrives. Transport value-add: overrides the base's croaking default (declared as the listen capability).

unlisten

$storage->unlisten($channel);

Unsubscribe from a notification channel.

notify

$storage->notify($channel, $payload?);

Send a PostgreSQL NOTIFY to the given channel with an optional payload. Returns a Future that resolves when the NOTIFY has been dispatched.

Unlike "listen", this does not require a dedicated connection — it uses a pooled connection from the normal pool.

copy_in

$storage->copy_in($table, \@columns, sub {
    my ($put) = @_;
    $put->(['Miles Davis', 'Jazz']);
    $put->(['John Coltrane', 'Jazz']);
});

Bulk load data via PostgreSQL COPY FROM STDIN. The callback receives a writer function that accepts arrayrefs of column values. Transport value-add: overrides the base's croaking default (declared as the copy capability).

deploy_async

my $future = $storage->deploy_async($schema, \%opts);
$future->get;

Deploys $schema via the native DBIO deploy pipeline, executing every DDL statement on EV::Pg inside a single async transaction. The Future resolves on success (COMMIT committed) or fails on the first DDL error (ROLLBACK rolled the whole batch back).

%opts:

add_drop_table => 1

Prepend DROP TABLE IF EXISTS ... CASCADE for every source in the schema, so a re-run on a populated database is idempotent. Default 0.

Requires $schema to expose pg_install_ddl — typically a DBIO::PostgreSQL schema (e.g. via use DBIO Schema => -pg). The DDL is generated locally from the schema classes (no DB roundtrips for DDL construction); only the execution hits libpq.

AUTHOR

DBIO Authors

COPYRIGHT AND LICENSE

Copyright (C) 2026 DBIO Authors

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.