Skip to content

Managing graphs

Every triple in pgRDF belongs to a named graph. A graph has an IRI, the name you use in SPARQL, and a numeric id, which the SQL functions take. This page covers creating graphs, finding them, copying and carving them, locking them, and checking their health.

The model in one paragraph

  • Create graphs by IRI with pgrdf.add_graph(iri). It returns the graph's id.
  • Graph 0 always exists. It is the default graph, named urn:pgrdf:graph:0.
  • A SPARQL query without a GRAPH clause searches all graphs. Scope it with GRAPH <iri> { … }. See querying.
  • Each graph is stored in its own partition, so dropping or clearing a whole graph is cheap regardless of its size.

Create and look up

sql
SELECT pgrdf.add_graph('http://example.org/people');  -- 1
SELECT pgrdf.add_graph('http://example.org/people');  -- 1 (idempotent)

SELECT pgrdf.graph_id('http://example.org/people');   -- 1
SELECT pgrdf.graph_iri(1);  -- http://example.org/people

Watch for NULL. graph_id() returns NULL for an IRI that does not exist. Most pgRDF functions are strict: given a NULL argument they return NULL without doing anything or raising an error. When a graph might be missing, check first:

sql
SELECT pgrdf.graph_id('http://example.org/typo') IS NOT NULL AS exists;

The IRI-taking functions (drop_graph(iri), clear_graph(iri), copy_graph(iri, iri), move_graph(iri, iri)) refuse unknown IRIs with SQLSTATE 42704 instead.

The inventory

pgrdf.graph_inventory() lists every graph with its size, lock state and reasoning freshness:

sql
SELECT * FROM pgrdf.graph_inventory();
--  graph_id |            iri            | asserted | inferred | locked | lock_reason | materialization
-- ----------+---------------------------+----------+----------+--------+-------------+-----------------
--         0 | urn:pgrdf:graph:0         |        0 |        0 | f      |             | never
--         1 | http://example.org/people |       14 |        8 | f      |             | stale
--         2 | http://example.org/shapes |       10 |        0 | f      |             | never
ColumnMeaning
assertedTriples you loaded or inserted.
inferredTriples produced by materialize.
locked, lock_reasonWhether the graph is write-locked, and why (see below).
materializationnever / current / stale / unknown. See reasoning.

Because it's a normal set-returning function, you can filter, join and aggregate it like a table:

sql
SELECT iri, asserted + inferred AS total
  FROM pgrdf.graph_inventory()
 ORDER BY total DESC LIMIT 5;

For a single number, pgrdf.count_quads(graph_id) returns asserted plus inferred triples for one graph.

pgrdf.orphan_partitions() lists storage partitions that no longer belong to any graph. It should return no rows. If it returns some, that storage can't be reached through SPARQL.

Clear, copy, move, drop

All four take either graph ids or graph IRIs.

FunctionEffectReturns
clear_graph(g)Removes every triple. The graph itself stays.triples removed
copy_graph(src, dst)Appends all of src into dst, including inferred triples.triples copied
move_graph(src, dst)Moves src into an empty dst, then removes src.triples moved
drop_graph(g, cascade => true)Removes the graph and its triples.triples removed
sql
SELECT pgrdf.add_graph('http://example.org/people-backup');
SELECT pgrdf.copy_graph('http://example.org/people', 'http://example.org/people-backup');
-- → 22

SELECT pgrdf.graph_digest(pgrdf.graph_id('http://example.org/people')) =
       pgrdf.graph_digest(pgrdf.graph_id('http://example.org/people-backup')) AS same_graph;
-- → true

Details that matter:

  • Copy by IRI needs the destination to exist. Create it with add_graph first, or you get 42704. Copy by id creates a missing destination for you, named urn:pgrdf:graph:<id>.
  • Copy appends. For an exact copy, clear the destination first. A copy's inferred triples show materialization = unknown. Run materialize on the copy if you rely on them.
  • Move refuses a non-empty destination (55000).
  • The default graph (0) can't be emptied with clear_graph. It returns 0 and leaves the triples in place. Keep data in named graphs.
  • Drop refuses over inferred triples when cascade => false (2BP01). The default is cascade => true. The default graph (0) can't be dropped.

The same operations are available as SPARQL UPDATE:

sparql
CREATE GRAPH <http://example.org/new>
CLEAR  GRAPH <http://example.org/staging>
DROP   GRAPH <http://example.org/staging>

INSERT { GRAPH <http://example.org/g2> { ?s ?p ?o } }
WHERE  { GRAPH <http://example.org/g1> { ?s ?p ?o } }

Carving out a subgraph

carve_graph copies part of a graph into another graph. It has two forms.

By predicate: every triple that uses one predicate.

sql
SELECT pgrdf.add_graph('http://example.org/social');
SELECT pgrdf.carve_graph(
  pgrdf.graph_id('http://example.org/people'),
  'http://xmlns.com/foaf/0.1/knows',
  pgrdf.graph_id('http://example.org/social'));
-- → 2

By neighbourhood: everything reachable from some seed nodes within max_hops steps (default 1).

sql
SELECT pgrdf.add_graph('http://example.org/alice-hood');
SELECT pgrdf.carve_graph(
  pgrdf.graph_id('http://example.org/people'),
  ARRAY['http://example.org/alice'],
  pgrdf.graph_id('http://example.org/alice-hood'),
  1);
-- NOTICE:  carve_graph: neighbourhood continues beyond max_hops=1 — 4 adjacent node(s)
--          were not expanded; the slice is the requested 1-hop ball, not a closed
--          component (raise max_hops to widen)
-- → 20

The notice tells you when the slice stopped at the hop limit rather than at the edge of the data.

Locking a graph

A lock makes a graph read-only until someone unlocks it. While it's held, every write path refuses with SQLSTATE 55P03: loading, SPARQL UPDATE, clear_graph, drop_graph, materialize, and being the destination of copy_graph / move_graph / carve_graph. Reads keep working.

sql
SELECT pgrdf.lock_graph(pgrdf.graph_id('http://example.org/people'), 'frozen for audit');

SELECT pgrdf.clear_graph('http://example.org/people');
-- ERROR:  55P03: pgrdf: graph 1 is locked (frozen for audit): clear_graph refused.
--         Unlock with pgrdf.unlock_graph(1, '<reason>').

SELECT pgrdf.unlock_graph(pgrdf.graph_id('http://example.org/people'), 'audit done');
  • A reason is required for both locking and unlocking.
  • Locking an already locked graph refuses (55P03), so a standing reason is never overwritten. Unlocking an unlocked graph refuses (55000).
  • The inventory's locked / lock_reason columns show current locks.
  • A lock is a coordination tool, not a security boundary. Use normal PostgreSQL roles and privileges to control who may write.

Checking a graph's health

pgrdf.graph_integrity(graph_id) checks that every triple is well-formed: no literal subjects, no blank-node or literal predicates, no references to missing terms.

sql
SELECT pgrdf.graph_integrity(pgrdf.graph_id('http://example.org/people'));
-- {"clean": true, "graph_id": 1, "illegal_terms": 0, "dangling_refs": 0,
--  "counts": {"quads": 22, "subject_literal": 0, "predicate_bnode": 0, ...}}

A graph that is not clean should be reloaded from source. Validation results on such a graph can't be trusted.

Looking up a term

Query results show terms by their text. If you hold a term id, for example from a report, pgrdf.get_term(id) returns its text.

pgRDF is released under the MIT license. Documentation built with VitePress, served via GitHub Pages.