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:
"sql_maker_class" / "_sql_maker_args" / "_post_insert_sql" -- the PostgreSQL SQLMaker and its
RETURNING *insert suffix"_transform_sql" --
?to positional$Nplaceholders, applied internally by the query seams (the core #70 / ADR 0032?-in contract)"_query_async" / "_query_async_pinned" -- native EV::Pg query dispatch (pooled and pinned)
"_run_pool_connect_statement" -- synchronous connect-action replay on a freshly-spawned EV::Pg pool connection (karr #68 seam)
"_pipeline_enter" / "_pipeline_sync" / "_pipeline_exit" -- libpq pipeline mode
"connect_info" / "_conninfo_string" -- the libpq conninfo transport shape
"_txn_context_class" / "_txn_conn_accessor" -- the pinned-connection transaction context
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:
on_connect_replay-- its pool (DBIO::PostgreSQL::EV::Pool, a DBIO::Storage::PoolBase) drives core's "_setup_pool_connection" in DBIO::Storage::Async on every freshly-spawned connection, and "_run_pool_connect_statement" replays the owning sync storage'son_connect_do/on_connect_callagainst it (karr #68).listen/notify-- LISTEN/NOTIFY ("listen", "unlisten", "notify").copy-- COPY FROM STDIN bulk load ("copy_in").pipeline-- libpq pipeline mode ("_pipeline_enter" / "_pipeline_sync" / "_pipeline_exit" under the inherited scaffold).
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 ... CASCADEfor 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.