DBIO-PostgreSQL-Age
view release on metacpan or search on metacpan
sets storage_type to DBIO::PostgreSQL::Age::Storage
* Storage
- DBIO::PostgreSQL::Age::Storage extends DBIO::PostgreSQL::Storage and
registers for the Pg driver
- connect_call_load_age connection callback: LOAD 'age' and set
search_path to include ag_catalog on each connection
* Graph lifecycle
- create_graph($name) creates a named graph
- drop_graph($name, $cascade) drops a graph, optionally cascading to
all vertices and edges
* Cypher queries
- cypher($graph, $query, \@columns, \%params) executes openCypher and
returns arrayrefs of hashrefs; result columns are declared as agtype
- graph name validated as a plain identifier and inlined as a SQL
literal, since Apache AGE requires the first cypher() argument to be
a name constant rather than a placeholder
- optional params hashref JSON-encoded and passed as AGE's third
cypher() argument for parameterized queries
[qw( person friend )],
);
DBIO core autodetects `dbi:Pg:` DSNs with the PostgreSQL driver, and
[DBIO::PostgreSQL::Age](https://metacpan.org/pod/DBIO::PostgreSQL::Age) is loaded via `load_components`.
## Apache AGE Features
**Graph Operations**
- `create_graph($name)` - create a named graph
- `drop_graph($name)` - drop a graph (cascade)
- `cypher($graph, $query, \@params)` - execute openCypher query
**openCypher Support**
- `MATCH`, `OPTIONAL MATCH` - graph pattern matching
- `WHERE` - filtering on node/relationship properties
- `RETURN`, `RETURN DISTINCT` - result projection
- `ORDER BY`, `SKIP`, `LIMIT` - pagination
- `WITH` - query chaining
- `CREATE`, `SET` - graph mutation
- `DELETE`, `DETACH DELETE` - graph deletion
docs/adr/0001-native-graph-surface-on-storage.md view on Meta::CPAN
connection), never through the relational ORM API and never via a second
transport object:
- `cypher($graph, $query, \@columns, \%params)` â executes one openCypher query
against the named graph (`Storage.pm:97-106`). It builds `SELECT * FROM
cypher('graph', $$ ... $$ [, ?]) AS (col agtype, ...)` and runs it through the
core `dbh_do` wrapper. **Every** result column is declared `agtype`
(`Storage.pm:119`) â AGE's only column type for `cypher()` output â and rows
come back as an arrayref of hashrefs of `agtype` strings for the caller to
decode (JSON for projected maps; the SYNOPSIS shows `JSON::MaybeXS`).
- `create_graph($name)` / `drop_graph($name, $cascade)` â graph lifecycle, thin
wrappers over `ag_catalog.create_graph` / `ag_catalog.drop_graph`
(`Storage.pm:66-85`).
The SQL/bind construction is split into a **pure** helper `_cypher_sql_bind`
(`Storage.pm:110-129`) so the generated SQL and binds are unit-tested with no
database (`t/20-cypher.t`); `cypher()` is the thin wrapper that adds execution.
This is the offline-testability seam for the one method that builds SQL by hand.
### Divergence from ADR 0017 point 2 (recorded, not hidden)
lib/DBIO/PostgreSQL/Age/Storage.pm view on Meta::CPAN
}
sub create_graph {
my ($self, $name) = @_;
$self->dbh->do('SELECT * FROM ag_catalog.create_graph(?)', undef, $name);
}
sub drop_graph {
my ($self, $name, $cascade) = @_;
$self->dbh->do(
'SELECT * FROM ag_catalog.drop_graph(?, ?)',
undef, $name, $cascade ? 1 : 0,
);
}
sub cypher {
my ($self, $graph, $query, $columns, $params) = @_;
my ($sql, $bind) = $self->_cypher_sql_bind($graph, $query, $columns, $params);
return $self->dbh_do(sub {
lib/DBIO/PostgreSQL/Age/Storage.pm view on Meta::CPAN
$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
=head1 DESCRIPTION
Extends L<DBIO::PostgreSQL::Storage> with Apache AGE graph database support.
Provides connection initialization, graph lifecycle management, and Cypher
query execution.
All result columns from C<cypher()> are declared as C<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.
lib/DBIO/PostgreSQL/Age/Storage.pm view on Meta::CPAN
=head2 create_graph
$storage->create_graph('social');
Creates a new Apache AGE graph with the given name.
=head2 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.
=head2 cypher
my $rows = $storage->cypher(
'social',
$$ MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a.name, b.name $$,
[qw( person friend )],
);
share/skills/dbio-postgresql-age/SKILL.md view on Meta::CPAN
If extension not installed:
```perl
$storage->dbh->do('CREATE EXTENSION IF NOT EXISTS age');
```
## Graph Lifecycle
```perl
$storage->create_graph('social');
$storage->drop_graph('social', 1); # 1 = cascade
```
Graph names must be plain PG identifiers (validated by `cypher()`):
```
valid: social, app_graph_1
invalid: app-graph, public.social, "graph name"
```
## Running Cypher
t/10-age-live.t view on Meta::CPAN
# --- cypher with parameters ---
my $alice = $schema->storage->cypher(
$graph,
q{ MATCH (p:Person {name: $name}) RETURN p.age },
['age'],
{ name => 'Alice' },
);
is(scalar @$alice, 1, 'parameterized query returns one row for Alice');
like($alice->[0]{age}, qr/30/, 'Alice age is 30');
# --- drop_graph (without cascade should fail if non-empty, in newer AGE) ---
# Skip the non-cascade case â depends on AGE version. Just drop with cascade.
lives_ok { $schema->storage->drop_graph($graph, 1) } 'drop_graph cascade lives';
my ($still_there) = $schema->storage->dbh->selectrow_array(
'SELECT 1 FROM ag_catalog.ag_graph WHERE name = ?',
undef, $graph,
);
ok(!$still_there, 'graph removed from ag_catalog.ag_graph');
done_testing;
( run in 1.844 second using v1.01-cache-2.11-cpan-7fcb06a456a )