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:

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.

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