psychologyPattern — Load → Reason → Query
Materialize what follows from a graph, then query the closure, so a query sees entailed facts as well as asserted ones.
When to use it
You need queries to return facts that follow from the ontology, such as subclass membership, transitive relations or inverses, not only the triples literally in the file. The amber Reason step runs in one session over one graph, so the graph has to fit your hardware. If it doesn't, use the carve pattern.
A worked scenario — an inverse property
ex:owns has an inverse, ex:ownedBy. We assert only that alice owns a book, never the inverse, and let the reasoner derive it.
Step 1 — load the data
SELECT pgrdf.add_graph('http://example.org/library'); -- → 1
SELECT pgrdf.parse_turtle('
@prefix ex: <http://example.org/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
ex:owns owl:inverseOf ex:ownedBy .
ex:alice ex:owns ex:book .
', pgrdf.graph_id('http://example.org/library')); -- → 2Step 2 — query before reasoning
Ask what alice owns by the inverse. Nothing is asserted that way yet:
SELECT * FROM pgrdf.sparql(
'PREFIX ex: <http://example.org/>
SELECT ?s WHERE { ?s ex:ownedBy ex:alice }');
-- → (0 rows)The inventory agrees: the graph has never been reasoned over.
SELECT iri, asserted, inferred, materialization
FROM pgrdf.graph_inventory() WHERE iri = 'http://example.org/library';
-- iri | asserted | inferred | materialization
-- ----------------------------+----------+----------+-----------------
-- http://example.org/library | 2 | 0 | neverStep 3 — reason
SELECT pgrdf.materialize(pgrdf.graph_id('http://example.org/library'), 'owl-rl');
-- → {"profile": "owl-rl", "base_triples": 2, "inferred_triples_written": 8,
-- "previous_inferred_dropped": 0, "reasoner_errors": [], "auto_analyzed": true,
-- "elapsed_ms": 0.962623, ...}The OWL 2 RL prp-inv rule entails ex:book ex:ownedBy ex:alice from ex:alice ex:owns ex:book and ex:owns owl:inverseOf ex:ownedBy. The other seven inferred triples are OWL 2 RL's own axioms: alice, book and owns are typed owl:Thing, and owl:Thing and owl:Nothing are declared as classes.
Step 4 — query after reasoning
The same query, now answered from the closure:
SELECT * FROM pgrdf.sparql(
'PREFIX ex: <http://example.org/>
SELECT ?s WHERE { ?s ex:ownedBy ex:alice }');{"s": "http://example.org/book"}SELECT iri, asserted, inferred, materialization
FROM pgrdf.graph_inventory() WHERE iri = 'http://example.org/library';
-- iri | asserted | inferred | materialization
-- ----------------------------+----------+----------+-----------------
-- http://example.org/library | 2 | 8 | currentThe profile changes the closure
owl:inverseOf is an OWL 2 RL rule, not an RDFS rule. Re-run with the RDFS profile and the inverse triple disappears:
SELECT pgrdf.materialize(pgrdf.graph_id('http://example.org/library'), 'rdfs')
->> 'inferred_triples_written' AS inferred;
-- → 0
SELECT * FROM pgrdf.sparql(
'PREFIX ex: <http://example.org/>
SELECT ?s WHERE { ?s ex:ownedBy ex:alice }');
-- → (0 rows)Each run replaces the previous inferred triples, so switching profiles is safe.
Keeping it current
Inferred triples are a snapshot. Add a triple and the inventory marks the materialization stale:
SELECT * FROM pgrdf.sparql(
'PREFIX ex: <http://example.org/>
INSERT DATA { GRAPH <http://example.org/library> { ex:bob ex:owns ex:pen } }');
SELECT materialization FROM pgrdf.graph_inventory() WHERE iri = 'http://example.org/library';
-- → staleRun materialize again, and both inverses are there:
SELECT pgrdf.materialize(pgrdf.graph_id('http://example.org/library'));
SELECT * FROM pgrdf.sparql(
'PREFIX ex: <http://example.org/>
SELECT ?thing ?owner WHERE { ?thing ex:ownedBy ?owner } ORDER BY ?thing');{"owner": "http://example.org/alice", "thing": "http://example.org/book"}
{"owner": "http://example.org/bob", "thing": "http://example.org/pen"}SELECT iri, asserted, inferred, materialization
FROM pgrdf.graph_inventory() WHERE iri = 'http://example.org/library';
-- iri | asserted | inferred | materialization
-- ----------------------------+----------+----------+-----------------
-- http://example.org/library | 3 | 11 | currentstale is decided by the asserted triple count. An edit that leaves the count unchanged (one delete plus one insert) still reads current, so re-run materialize after any change you care about. See Is the materialization current?.
Next step
Add a conformance gate over the closure with Load → Validate → Query, or right-size a large source with Ingest → Carve → Reason.