NAME

DBIO::PostgreSQL::Age::Storage - PostgreSQL storage with Apache AGE graph support

VERSION

version 0.900001

SYNOPSIS

# Loaded automatically via DBIO::PostgreSQL::Age component.
# Use connect_call_load_age to initialize AGE on each connection:

MyApp::Schema->connect(
  $dsn, $user, $pass,
  { on_connect_call => 'load_age' },
);

my $storage = $schema->storage;

$storage->create_graph('social');

my $rows = $storage->cypher(
  'social',
  $$ MATCH (a:Person {name: $name})-[:KNOWS]->(b) RETURN b.name $$,
  ['friend'],
  { name => 'Alice' },
);

$storage->drop_graph('social', 1);  # cascade

DESCRIPTION

A storage layer that adds Apache AGE graph database support -- connection initialization, graph lifecycle management, and Cypher query execution -- to a PostgreSQL storage. It is not a storage subclass: it is a plain method package composed over the resolved driver storage at connection time (see DBIO::Storage::Composed). DBIO::PostgreSQL::Age registers it via "register_storage_layer" in DBIO::Schema, so on $schema->connect the live storage isa both this layer and DBIO::PostgreSQL::Storage, and the methods below are callable on that composed storage. Because it is a layer, it stacks cleanly with other extension layers (its methods are disjoint from, e.g., DBIO::PostgreSQL::PostGIS::Storage).

Layer rules

Per the storage-layer composition model (DBIO core, karr #70): this package does not use base a driver storage, defines no constructor, and calls only the documented public storage surface -- dbh_do and throw_exception for the graph methods, plus the connect-action seam _do_query for connect_call_load_age -- each resolved through the composed MRO to the driver base at runtime. _do_query is deliberate: it is the same seam connect_call_do_sql uses, and it is what routes the LOAD 'age' replay onto each freshly-spawned async pool connection (core karr #68); a bare $self->dbh->do would run against the sync dbh instead and defeat the replay. The pure helpers _cypher_sql_bind and decode_agtype are DB-free class-level helpers, shared by composition with the async layer (DBIO::PostgreSQL::Age::Storage::Async) so sync and async build identical SQL and decode identically.

All result columns from cypher() are declared as agtype — Apache AGE's JSON-superset type that represents vertices, edges, paths, and scalar values. Values are returned as strings and can be decoded with a JSON parser.

METHODS

connect_call_load_age

{ on_connect_call => 'load_age' }

Connection callback that loads the Apache AGE shared library into the session and sets search_path to include ag_catalog. Must be called before any graph operations.

create_graph

$storage->create_graph('social');

Creates a new Apache AGE graph with the given name.

drop_graph

$storage->drop_graph('social');
$storage->drop_graph('social', 1);  # cascade

Drops the named graph. Pass a true second argument to cascade the drop to all vertices and edges within the graph.

cypher_async

create_graph_async

drop_graph_async

The async counterparts of "cypher", "create_graph" and "drop_graph", reachable on the live (composed) storage. Each dispatches through core's DBIO::Storage/_run_async: on an async connection ({ async => 'future_io' }, 'ev', ...) they route to the embedded async backend (the composed DBIO::PostgreSQL::Age::Storage::Async layer, which does the real non-blocking work); under { async => 'immediate' } they run the sync method in-process and return an immediately-resolved Future with no event loop; on a plain sync connection they croak. This mirrors the core CRUD *_async methods ("select_async" in DBIO::Storage and friends) -- graph queries have a sync equivalent, so they degrade the same way rather than being backend-only.

decode_agtype

my $name = $storage->decode_agtype('"alice"');         # "alice"
my $age  = $storage->decode_agtype('30');              # 30
my $v    = $storage->decode_agtype(
  '{"id": 1, "label": "Person", "properties": {"name": "alice"}}::vertex'
);                                                       # { id => 1, label => "Person", properties => { name => "alice" } }

my $rows = $storage->cypher(
  'social',
  $$ RETURN n $$,
  ['n'],
);
my $vertex = $storage->decode_agtype($rows->[0]{n});   # manual decode

Decodes a single agtype text value (as returned by cypher()) into native Perl data. Recognised shapes:

  • String scalar (quoted, e.g. "alice") — string with quotes stripped

  • Integer / float scalar (e.g. 42, 3.14) — Perl number

  • Boolean (true, false) — JSON::MaybeXS true/false objects

  • Null (null) — undef

  • Map / list (e.g. {"name": "alice"}, [1, 2, 3]) — hashref / arrayref

  • Vertex / edge — same as the underlying map; a trailing ::vertex / ::edge cast annotation (only emitted by older AGE versions) is stripped before decoding. id, label, start_id, end_id, properties are preserved as JSON keys.

  • Path — arrayref of decoded vertices and edges (structure preserved, not unwrapped)

  • Anything else — returned as-is so the caller can post-process it

cypher

my $rows = $storage->cypher(
  'social',
  $$ MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a.name, b.name $$,
  [qw( person friend )],
);

# With Cypher parameters:
my $rows = $storage->cypher(
  'social',
  $$ MATCH (n:Person {name: $name}) RETURN n $$,
  ['node'],
  { name => 'Alice' },
);

# Auto-decode each cell into native Perl data:
my $rows = $storage->cypher(
  'social',
  $$ MATCH (n:Person {name: $name}) RETURN n $$,
  ['node'],
  { name => 'Alice' },
  { auto_decode => 1 },
);
# $rows->[0]{node} is now a hashref, not a string.

Executes a Cypher query against the named graph. $columns is an arrayref of result column names; all are declared as agtype. Returns an arrayref of hashrefs with one key per column.

An optional $params hashref is JSON-encoded and passed as AGE's third argument to cypher() for parameterized queries.

Pass an optional fifth argument, a hashref of options, to control result handling:

  • auto_decode => 1 — apply "decode_agtype" to every cell of every row before returning. Without this option, every cell is a raw agtype string and decoding is the caller's responsibility.

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.

2 POD Errors

The following errors were encountered while parsing the POD:

Around line 259:

alternative text 'DBIO::Storage/_run_async' contains non-escaped | or /

Around line 357:

Unknown directive: =seealso