NAME
DBIO::PostgreSQL::Age::Storage::Async - Floating async storage layer for Apache AGE (cypher_async)
VERSION
version 0.900001
SYNOPSIS
# Composed automatically for an Age schema opened in ANY async mode whose
# transport can replay LOAD 'age' -- future_io, ev, ... (core karr #70).
my $schema = MyApp::Schema->connect(
$dsn, $user, $pass,
{ on_connect_call => 'load_age', async => 'future_io' }, # or 'ev'
);
my $async = $schema->storage->async; # the composed async backend
$async->create_graph_async('social')->then(sub {
$async->cypher_async(
'social',
$$ MATCH (n:Person {name: $name}) RETURN n $$,
['node'],
{ name => 'Alice' },
{ auto_decode => 1 },
);
})->then(sub {
my ($rows) = @_; # arrayref of hashrefs, one key per column, already decoded
...
});
DESCRIPTION
The floating async storage layer for Apache AGE. It is a plain method package -- not a transport and not a subclass of any transport. Core's storage-layer composition (karr #70) mirrors the registered sync Age layer onto its async sibling by convention (DBIO::PostgreSQL::Age::Storage -> ...::Async) and composes this package (C3) OVER whatever transport the connection's async mode resolves: DBIO::PostgreSQL::Storage::Async for future_io, DBIO::PostgreSQL::EV::Storage for ev. The composed backend isa both this layer and that transport, so cypher_async rides every capable transport without a per-transport class.
Required transport capability
AGE mandates LOAD 'age' (and the ag_catalog search_path SET) on every pooled connection, replayed through core's pool on_connect seam. This layer therefore declares on_connect_replay as a "required_transport_capabilities"; core's capability gate croaks loudly if a schema tries to compose AGE over a transport that cannot replay connect actions, rather than silently losing the session setup. Both shipped PostgreSQL async transports advertise on_connect_replay.
Separate sync and async entry points
Graph queries have two entry points, on the two composed storages:
sync --
$schema->storage->cypher(...), on the composed sync storage (the DBIO::PostgreSQL::Age::Storage layer over DBIO::PostgreSQL::Storage, a DBIO::Storage::DBI), returns an arrayref of hashrefs.async --
$schema->storage->async->cypher_async(...), on the composed async backend (this layer over a DBIO::Storage::Async transport), returns a Future resolving to the same arrayref of hashrefs. As a convenience the composed sync storage also carries acypher_asyncdispatcher (see "cypher_async" in DBIO::PostgreSQL::Age::Storage):$schema->storage->cypher_async(...)routes to this backend on an async connection and degrades in-process under{ async => 'immediate' }, exactly like the core CRUD*_asyncmethods.
The sync and async surfaces stay separate by design: this layer is not a subclass of DBIO::PostgreSQL::Age::Storage, and it never use bases a storage. Welding the blocking DBI machinery (dbh, dbh_do, txn_do, the driver registry) onto an async transport is exactly what the layer model avoids -- the reason is unchanged, but the mechanism is now composition-by-core, not a parallel-inheritance adapter bolted to one transport. What crosses between the two surfaces is only the two pure, DB-free helpers of the sync layer -- the SQL builder _cypher_sql_bind and decode_agtype -- reused by composition, called as class-level helpers so both entry points build identical SQL and decode identically. auto_decode therefore behaves identically on both.
Session setup (LOAD 'age')
This layer does nothing locally for LOAD 'age' -- it defines no connect_call_load_age. The setup rides core's pool on_connect seam (karr #68): a schema connected with { on_connect_call => 'load_age' } gives the pool an owning sync storage (the composed sync Age storage, which defines connect_call_load_age on its layer), and core replays that connect action synchronously against each freshly-spawned pool connection -- on whichever transport was composed under this layer. Installing any LOAD logic on this layer would be the karr #66 anti-pattern.
The '?' placeholder seam
cypher_async / create_graph_async / drop_graph_async hand the transport plain ?-placeholder SQL. Shaping ? to the transport's wire dialect (e.g. DBD::Pg's positional $N) is the transport's concern, applied inside its _query_async (core karr #70 / ADR 0032 '?'-seam). The $$...$$ Cypher body and any $name Cypher parameter reference are not SQL placeholders and survive untouched; only a real ? is rewritten, and only by the transport.
Everything else -- the transport, the connection pool, the CRUD runner, transactions and the SQLMaker -- comes from whichever transport this layer is composed over.
METHODS
required_transport_capabilities
my @caps = DBIO::PostgreSQL::Age::Storage::Async->required_transport_capabilities;
# ('on_connect_replay')
Class method. The transport capabilities this async layer requires. Returns on_connect_replay because LOAD 'age' must replay on every pooled connection. Core's composition capability gate (see "transport_capabilities" in DBIO::Storage::Async) croaks naming this layer, the missing capability and the transport if AGE is composed over a transport that does not advertise it.
cypher_async
my $future = $async->cypher_async(
'social',
$$ MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a.name, b.name $$,
[qw( person friend )],
);
# With Cypher parameters:
my $future = $async->cypher_async(
'social',
$$ MATCH (n:Person {name: $name}) RETURN n $$,
['node'],
{ name => 'Alice' },
);
# Auto-decode each cell into native Perl data (identical to sync cypher):
my $future = $async->cypher_async(
'social',
$$ MATCH (n:Person {name: $name}) RETURN n $$,
['node'],
{ name => 'Alice' },
{ auto_decode => 1 },
);
The async counterpart of "cypher" in DBIO::PostgreSQL::Age::Storage. Builds the same SQL and binds via the shared _cypher_sql_bind and executes them over the composed transport, returning a Future that resolves to an arrayref of hashrefs (one key per $columns entry) -- exactly the shape sync cypher() returns.
$params, if given, is JSON-encoded and passed as AGE's third cypher() argument. With { auto_decode => 1 } every cell is passed through "decode_agtype" in DBIO::PostgreSQL::Age::Storage inside the Future chain, so the resolved arrayref is already decoded -- identical semantics to sync auto_decode, just async. Without it every cell is a raw agtype string and decoding is the caller's responsibility.
create_graph_async
my $future = $async->create_graph_async('social');
Async counterpart of "create_graph" in DBIO::PostgreSQL::Age::Storage: a thin wrapper that runs ag_catalog.create_graph(?) over the composed transport. Returns a Future.
drop_graph_async
my $future = $async->drop_graph_async('social');
my $future = $async->drop_graph_async('social', 1); # cascade
Async counterpart of "drop_graph" in DBIO::PostgreSQL::Age::Storage: a thin wrapper that runs ag_catalog.drop_graph(?, ?) over the composed transport. Pass a true second argument to cascade the drop. Returns a Future.
DBIO::PostgreSQL::Age::Storage - the sync storage layer with
cypher()DBIO::PostgreSQL::Storage::Async - the
future_iotransport this layer composes overDBIO::PostgreSQL::EV::Storage - the
evtransport this layer composes overDBIO::PostgreSQL::Age - Schema component that activates AGE
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.
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 260:
Unknown directive: =seealso