Skip to content

psychologyReasoning — OWL 2 RL and RDFS ​

pgrdf.materialize(graph_id BIGINT, profile TEXT DEFAULT 'owl-rl') → JSONB computes everything that follows from one graph's triples and stores the result in the same graph, flagged as inferred. The profile argument picks the rule set:

  • 'owl-rl' (default): OWL 2 RL, run by the reasonable reasoner.
  • 'rdfs': the RDFS closures only (subclass, subproperty, domain, range).

Re-running replaces the previous inferred triples. Asserted triples are never changed. After the write, materialize refreshes planner statistics (setting pgrdf.auto_analyze, on by default), so queries over the enlarged graph stay fast.

At a glance ​

sql
SELECT pgrdf.add_graph('urn:example:people');
SELECT pgrdf.parse_turtle('
@prefix ex:   <http://example.com/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

ex:Engineer rdfs:subClassOf ex:Person .
ex:Person   rdfs:subClassOf ex:Agent .
ex:alice    a               ex:Engineer .
', pgrdf.graph_id('urn:example:people'));

SELECT pgrdf.materialize(pgrdf.graph_id('urn:example:people'));
-- {"diff_ms": 0.002, "load_ms": 0.386, "profile": "owl-rl", "write_ms": 0.276,
--  "reason_ms": 0.096, "analyze_ms": 0.202, "elapsed_ms": 1.075,
--  "base_triples": 3, "auto_analyzed": true, "reasoner_errors": [],
--  "inferred_triples_written": 10, "previous_inferred_dropped": 0}

ex:alice is now also an ex:Person and an ex:Agent, and SPARQL returns those types like any other triple. The worked example walks through it step by step.

The return value ​

FieldMeaning
profileThe rule set that ran.
base_triplesAsserted triples read as input.
inferred_triples_writtenEntailed triples, not already asserted, written by this call.
previous_inferred_droppedInferred triples from the previous run that this call replaced.
reasoner_errorsProblems reported by the reasoner; an empty array when there are none.
auto_analyzedWhether planner statistics were refreshed after the write.
elapsed_msTotal time for the call.
load_ms, reason_ms, diff_ms, write_ms, analyze_msTime spent reading the graph, reasoning, separating new triples from asserted ones, writing, and refreshing statistics.

Timings vary with hardware; the counts don't.

Where inferred triples show up ​

FunctionInferred triples
sparql, construct, describeIncluded. Queries see asserted and inferred triples as one set.
validateIncluded. Shapes are checked against the entailed closure.
graph_inventory()Counted separately, in the inferred column. count_quads returns asserted plus inferred.
export_graph, graph_digest, structural_digestExcluded. They cover asserted triples only.
copy_graphCarried to the destination. The copy's materialization reads unknown.
drop_graph(g, cascade => false)Refuses with SQLSTATE 2BP01 while the graph holds inferred triples. The default, cascade => true, drops them with the graph.

Is the materialization current? ​

Inferred triples are a snapshot. They don't change when you load or delete asserted triples; they change when you run materialize again. pgrdf.graph_inventory() tells you, per graph, whether that snapshot still matches:

sql
SELECT iri, asserted, inferred, materialization
  FROM pgrdf.graph_inventory()
 WHERE iri LIKE 'urn:example:%';
--           iri           | asserted | inferred | materialization
-- ------------------------+----------+----------+-----------------
--  urn:example:people      |        4 |       13 | current
--  urn:example:people-copy |        4 |       13 | unknown
materializationMeaning
nevermaterialize has not run on this graph.
currentThe asserted triple count is the same as at the last run.
staleAsserted triples were added or removed since the last run.
unknownThe graph holds inferred triples with no run recorded for them, for example after copy_graph.

To refresh a stale or unknown graph, run materialize on it again:

sql
SELECT pgrdf.parse_turtle('@prefix ex: <http://example.com/> . ex:bob a ex:Engineer .',
                          pgrdf.graph_id('urn:example:people'));
SELECT materialization FROM pgrdf.graph_inventory() WHERE iri = 'urn:example:people';
-- stale

SELECT pgrdf.materialize(pgrdf.graph_id('urn:example:people'));
SELECT materialization FROM pgrdf.graph_inventory() WHERE iri = 'urn:example:people';
-- current

Freshness follows the asserted count

The check compares the number of asserted triples. An edit that leaves the count unchanged, such as deleting one triple and inserting another in the same transaction, still reads current. If you change a graph that way, run materialize anyway. It is always safe to re-run.

Refusals ​

SituationSQLSTATEMessage
Unknown profile22023materialize: unknown profile "bogus" (supported: 'owl-rl', 'rdfs')
Graph is locked55P03pgrdf: graph 1 is locked (release review): materialize refused. Unlock with pgrdf.unlock_graph(1, '<reason>').

See Errors and diagnostics for handling SQLSTATEs in your driver.

Reason over a right-sized graph ​

Loading is parallel and scales to billions of triples through the staged loader. Reasoning is single-threaded per graph, so run materialize on a graph sized for your hardware and batch window. To reason over part of a large graph, copy that part out with carve_graph and materialize the slice. Scale of reasoning has measured numbers.

Topics ​

Learn more ​

Next: Mental model →

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