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):

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 ->all yields (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.