DBIO-PostgreSQL-Age

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

          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

README.md  view on Meta::CPAN

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