The mental model
You write a SHACL shapes graph that says, in Turtle:
"Every
foaf:Personmust have at least onefoaf:name, and the name must be a string. Everyfoaf:Personmust have at least onefoaf:mbox, and the mailbox must be an IRI."
You load the data graph and the shapes graph into pgRDF as two separate graphs. pgrdf.validate(data, shapes) returns a report listing every result, with:
- the focus node: the node being validated;
- the path: the property the constraint is about;
- the value that failed, when there is one;
- the source shape and constraint component: which rule fired;
- the severity and a human-readable message.
The report shape
{
"conforms": false,
"results": [
{
"focusNode": "http://example.org/bob",
"resultPath": "http://xmlns.com/foaf/0.1/mbox",
"value": null,
"sourceShape": "_:e0426eab19040cf3a29413efb8e7cad7",
"sourceConstraintComponent": "http://www.w3.org/ns/shacl#MinCountConstraintComponent",
"resultSeverity": "sh:Violation",
"resultMessage": "MinCount(1) not satisfied"
}
],
"mode": "native",
"data_triples": 5,
"shapes_triples": 10,
"data_graph_id": 5,
"shapes_graph_id": 6,
"elapsed_ms": 1.42
}When the data conforms, results is empty:
{"conforms": true, "results": [], "mode": "native", "data_triples": 6,
"shapes_triples": 10, "data_graph_id": 5, "shapes_graph_id": 6, "elapsed_ms": 0.72}conforms is true only when results is empty. Any result makes it false, including one with severity sh:Warning or sh:Info. To let warnings through, filter results on resultSeverity yourself. See Report as data.
Check your graph ids
Strict mode (the default) refuses a shapes graph that declares no targets, so a wrong numeric shapes id fails loudly instead of passing.
A NULL id behaves differently. Like most pgRDF functions, validate returns NULL when an argument is NULL, and pgrdf.graph_id() returns NULL for an IRI that doesn't exist. A mistyped IRI therefore gives you NULL, not an error:
SELECT pgrdf.validate(pgrdf.graph_id('http://example.org/data'),
pgrdf.graph_id('http://example.org/typo')) IS NULL;
-- trueTreat a NULL report as a failure in any gate.
Validation over inferred triples
Validation sees inferred triples as well as asserted ones. After pgrdf.materialize, shapes are checked against the entailed closure:
SELECT pgrdf.add_graph('http://example.org/team');
SELECT pgrdf.add_graph('http://example.org/team-shapes');
SELECT pgrdf.parse_turtle('
@prefix ex: <http://example.org/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
ex:Engineer rdfs:subClassOf <http://xmlns.com/foaf/0.1/Person> .
ex:erin a ex:Engineer .
', pgrdf.graph_id('http://example.org/team'));
SELECT pgrdf.parse_turtle('
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix ex: <http://example.org/> .
ex:PersonShape a sh:NodeShape ;
sh:targetClass foaf:Person ;
sh:property [ sh:path foaf:name ; sh:minCount 1 ] .
', pgrdf.graph_id('http://example.org/team-shapes'));
SELECT pgrdf.validate(pgrdf.graph_id('http://example.org/team'),
pgrdf.graph_id('http://example.org/team-shapes')) ->> 'conforms';
-- true (erin is not yet known to be a foaf:Person)
SELECT pgrdf.materialize(pgrdf.graph_id('http://example.org/team'), 'rdfs');
SELECT pgrdf.validate(pgrdf.graph_id('http://example.org/team'),
pgrdf.graph_id('http://example.org/team-shapes')) ->> 'conforms';
-- false (erin is now an inferred foaf:Person with no foaf:name)If you validate after changing the data, check that the materialization is still current.
Validation as a gate
Because the report is JSONB, a pipeline can gate on the verdict:
SELECT CASE WHEN coalesce((rep ->> 'conforms')::boolean, false)
THEN 'OK'
ELSE 'REJECT'
END AS verdict
FROM (SELECT pgrdf.validate(pgrdf.graph_id('http://example.org/data'),
pgrdf.graph_id('http://example.org/shapes')) AS rep) r;coalesce(…, false) turns a NULL report into a rejection. A failed validation can be logged, alerted on, or used to roll back the load inside the same transaction. See Report as data.