NAME
DBIO::PostgreSQL::Storage::Async - future_io async PostgreSQL transport over DBD::Pg's pg_async binding
VERSION
version 0.900001
DESCRIPTION
The concrete future_io transport adapter for PostgreSQL (core ADR 0030 / 0031). It is resolved by convention off the sync driver storage: a connection opened with
MyApp::Schema->connect($dsn, $user, $pass, { async => 'future_io' });
on a DBIO::PostgreSQL::Storage instance derives ref($storage) . '::Async' == DBIO::PostgreSQL::Storage::Async. No register_async_mode call is needed; the name itself is the registration.
Storage extensions ride on top by composition
Under the storage-layer composition model (core karr #70), a PostgreSQL extension (AGE, PostGIS, ...) is not a storage_type subclass and does not ship a shadow register_async_mode / ::Storage::Async of its own. It ships a plain storage layer registered with $schema->register_storage_layer('DBIO::PostgreSQL::Ext::Storage') and, for async behaviour, a sibling async mirror DBIO::PostgreSQL::Ext::Storage::Async (a plain method package, not a transport). When a layered schema connects { async => 'future_io' }, core resolves this transport by convention off the composition base (the driver, not the layers -- see "_async_resolution_class" in DBIO::Storage::DBI) and then DBIO::Storage::Composed->composes each registered layer's async mirror on top of it. So this class stays the single per-driver future_io transport BASE; extensions add behaviour above it via C3, exactly as their sync layers ride the sync driver storage.
The Model-B orchestration -- the CRUD runner ("_run_crud" in DBIO::Storage::Async with its pooled / pinned runners), INSERT returned-columns mapping, "txn_do_async" in DBIO::Storage::Async bracketing, the pipeline scaffold, and the sync ->get fallbacks -- is inherited unchanged from DBIO::Storage::Async. The Future::IO transport (query execution over the socket-readable watcher, the Future class and the DBIO::Async::Pool) is inherited from DBIO::Async::Storage. This class fills only the DB-specific transport seams over DBD::Pg's own asynchronous binding ({pg_async => PG_ASYNC} + pg_ready / pg_result / pg_socket):
"sql_maker_class" / "_sql_maker_args" -- the PostgreSQL SQLMaker
"_transform_sql" --
?to positional$Nplaceholders"_post_insert_sql" --
RETURNING *for the returned-columns hashref"_normalize_conninfo" -- DBI-form connect info into the pool's shape
"_create_pool_connection" / "_shutdown_pool_connection" -- DBD::Pg handle lifecycle
"_conn_ready" / "_conn_fileno" -- readiness + the
pg_socketfd for the Future::IO watcher"_submit_query" / "_collect_result" -- send non-blocking, then gather once the wire is readable
"_txn_context_class" / "_txn_conn_accessor" -- the pinned-connection transaction context
A connection is represented as a small hashref { dbh => $dbh, sth => $sth }: the DBD::Pg database handle plus the statement handle of the in-flight async query (there is at most one per connection at a time, guaranteed by the pool / transaction pinning).
Transport capabilities
This transport inherits transport_capabilities from DBIO::Async::Storage, which advertises exactly on_connect_replay (the pool replays the owning sync storage's on_connect_do / on_connect_call on every freshly-spawned connection). It declares nothing extra: the advanced PostgreSQL wire features are not implemented on the future_io transport.
LISTEN/NOTIFY ("listen" in DBIO::Storage::Async), COPY, and pipelining ("pipeline" in DBIO::Storage::Async) are not supported here -- DBD::Pg's async binding exposes no libpq pipeline mode, and the base seams croak if called. The native ev backend (DBIO::PostgreSQL::EV::Storage, dist dbio-postgresql-ev) is the transport that carries LISTEN/NOTIFY, COPY and pipelining. An async storage layer that declares required_transport_capabilities for any of these will therefore fail loud (core capability gate) when composed over this future_io transport, naming the missing capability -- never a silent feature loss.
METHODS
sql_maker_class
The DBIO::PostgreSQL::SQLMaker subclass used to generate SQL.
_sql_maker_args
PostgreSQL SQLMaker construction args: double-quote identifier quoting, . name separator, LIMIT/OFFSET dialect. Matches the sync driver and the ev backend.
_transform_sql
Rewrite ? placeholders to PostgreSQL positional $N, skipping quoted literals / identifiers and the JSONB @? operator.
_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).
_normalize_conninfo
my $info = $storage->_normalize_conninfo([ 'dbi:Pg:...', $user, $pass, \%attrs ]);
Convert the sync storage's DBI-form connect info into the [ \%conninfo, \%opts ] pair the pool consumes, carrying the DSN / user / password / attrs straight through for "_create_pool_connection" to hand to DBI->connect. A pool_size attribute, if present, is lifted into the conninfo hash (the inherited normaliser strips it back out to size the pool). Broker-style or already-normalised info is passed through untouched.
_create_pool_connection
my $conn = $storage->_create_pool_connection(\%conninfo);
Open one DBD::Pg handle via DBI->connect and wrap it as { dbh => $dbh }. AutoCommit is on: the inherited orchestration drives transactions with explicit BEGIN / COMMIT / ROLLBACK on the pinned connection, so DBD::Pg's own transaction tracking stays out of the way.
_shutdown_pool_connection
Disconnect the DBD::Pg handle held by $conn.
_conn_ready
True once the connection can accept queries. DBI->connect is a blocking connect, so a handed-out connection is ready immediately -- we only confirm the handle is live.
_conn_fileno
The connection socket fd for the Future::IO readable watcher, exposed by DBD::Pg as $dbh->{pg_socket}. The transport base ("_await_readable" in DBIO::Async::Storage) dups this integer fd into a filehandle for Future::IO->poll.
_submit_query
$storage->_submit_query($conn, $sql, $bind);
Send $sql non-blocking via DBD::Pg's async binding: prepare with {pg_async => PG_ASYNC} and execute the bind values. execute returns immediately; the result is gathered later by "_collect_result". The statement handle is stashed on $conn so the collector can fetch its rows.
pg_placeholder_dollaronly is set so DBD::Pg treats only the $N placeholders "_transform_sql" produced as bind parameters, leaving any literal ? (the JSONB @? operator) alone.
_collect_result
my $future = $storage->_collect_result($conn, $sql, $bind);
Called once the socket is readable. If the async result has not fully arrived ($dbh->pg_ready is false) it waits for the socket again and re-checks -- so a large result spanning several reads is handled correctly. Once ready it finalises with $dbh->pg_result and resolves:
a query that produced a result set (SELECT, or INSERT/UPDATE/DELETE
... RETURNING) -> the list of raw row arrayrefs, exactly the shape the sync cursor's->allyields (ADR 0031 §3);a statement with no result set (plain UPDATE/DELETE, BEGIN/COMMIT/...) -> the affected-row count from
pg_result.
The full list is carried in an explicit future_class->done(@rows) so it survives the surrounding ->then chain (a bare list return would collapse to its last element).
_txn_context_class
The pinned-connection transaction context handed to a txn_do_async coderef: the future_io DBIO::Async::TransactionContext.
_txn_conn_accessor
The constructor key the pinned connection is passed under -- txn_conn, matching DBIO::Storage::Async::TransactionContext.
AUTHOR
DBIO & DBIx::Class Authors
COPYRIGHT AND LICENSE
Copyright (C) 2026 DBIO Authors Portions Copyright (C) 2005-2025 DBIx::Class Authors Based on DBIx::Class, heavily modified.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.