verifiedPattern — Load → Validate → Query
Check a graph against SHACL shapes and act on the verdict: gate a load, raise an alert, or query only what conforms. This page walks the whole journey on real data, with the real report.
When to use it
You want a conformance gate: refuse data that violates your shapes, or surface the violations as data. Put Reason before Validate when the shapes constrain entailed facts, and skip it when they constrain only asserted triples. Both steps run in one session over one graph, so the graph has to fit your hardware.
A worked scenario — required fields
Every person must have a name and an age. We load two records, one complete and one missing its age, and watch validation catch exactly the incomplete one.
Step 1 — load the data
SELECT pgrdf.add_graph('http://example.org/staff'); -- → 1
SELECT pgrdf.parse_turtle('
@prefix ex: <http://example.org/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:alice a foaf:Person ; foaf:name "Alice" .
ex:bob a foaf:Person ; foaf:name "Bob" ; ex:age "30"^^xsd:integer .
', pgrdf.graph_id('http://example.org/staff')); -- → 5Alice has a name but no age; Bob has both.
Step 2 — load the shapes into their own graph
SELECT pgrdf.add_graph('http://example.org/staff-shapes'); -- → 2
SELECT pgrdf.parse_turtle('
@prefix ex: <http://example.org/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:PersonShape a sh:NodeShape ;
sh:targetClass foaf:Person ;
sh:property [ sh:path foaf:name ; sh:minCount 1 ; sh:datatype xsd:string ] ;
sh:property [ sh:path ex:age ; sh:minCount 1 ; sh:datatype xsd:integer ] .
', pgrdf.graph_id('http://example.org/staff-shapes')); -- → 10Every foaf:Person must carry at least one string name and one integer age.
Step 3 — validate
SELECT pgrdf.validate(pgrdf.graph_id('http://example.org/staff'),
pgrdf.graph_id('http://example.org/staff-shapes'));pgRDF returns the validation report as JSONB:
{
"conforms": false,
"results": [
{
"focusNode": "http://example.org/alice",
"resultPath": "http://example.org/age",
"value": null,
"sourceShape": "_:f25130e0…",
"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": 1,
"shapes_graph_id": 2,
"elapsed_ms": 1.421415
}Reading the report
conformsisfalse: at least one node violates the shapes.- One result, and it's Alice: on
ex:age, ash:Violationfrom theminCountconstraint. She's missing the required age.valueisnullbecause there's no value to report. sourceShapeis the property shape that fired. It's written with[ … ], so it's a blank node with a generated label.- Bob isn't in
results: he conforms, so a gate built on this report lets him through and stops Alice.
Acting on the report
The report is JSONB, so the gate is plain SQL:
SELECT (pgrdf.validate(pgrdf.graph_id('http://example.org/staff'),
pgrdf.graph_id('http://example.org/staff-shapes')) ->> 'conforms')::boolean AS ok;
-- → fBranch on ok to accept or reject the load, or unfold the violations to act on each one:
SELECT res ->> 'focusNode' AS node,
res ->> 'resultPath' AS path,
res ->> 'resultMessage' AS message
FROM jsonb_array_elements(
pgrdf.validate(pgrdf.graph_id('http://example.org/staff'),
pgrdf.graph_id('http://example.org/staff-shapes')) -> 'results') AS res;
-- node | path | message
-- --------------------------+------------------------+---------------------------
-- http://example.org/alice | http://example.org/age | MinCount(1) not satisfiedFix the data and validate again:
SELECT * FROM pgrdf.sparql(
'PREFIX ex: <http://example.org/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
INSERT DATA { GRAPH <http://example.org/staff> { ex:alice ex:age "41"^^xsd:integer } }');
SELECT pgrdf.validate(pgrdf.graph_id('http://example.org/staff'),
pgrdf.graph_id('http://example.org/staff-shapes')) ->> 'conforms' AS conforms;
-- → trueSee Report as data for more ways to query reports.
A wrong shapes graph is refused, not passed
By default validate is strict. Pass the wrong graph id for the shapes (here 0, the empty default graph) and you get an error, not a hollow conforms: true:
ERROR: validate: shapes graph 0 declares no SHACL target (0 triples). Nothing would be
selected, so a verdict would be vacuous — ...Variation — validate the entailed graph
Validation checks the triples that are in the graph. It doesn't apply rdfs:domain or rdfs:subClassOf by itself. Add a person who is a person only by implication:
SELECT pgrdf.parse_turtle('
@prefix ex: <http://example.org/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
ex:worksOn rdfs:domain foaf:Person .
ex:dave ex:worksOn ex:apollo ; foaf:name "Dave" .
', pgrdf.graph_id('http://example.org/staff'));
SELECT pgrdf.validate(pgrdf.graph_id('http://example.org/staff'),
pgrdf.graph_id('http://example.org/staff-shapes')) ->> 'conforms' AS conforms;
-- → true (dave isn't a foaf:Person yet, so the shape doesn't target him)Reason first, so validation sees the closure:
SELECT pgrdf.materialize(pgrdf.graph_id('http://example.org/staff'));
SELECT res ->> 'focusNode' AS node, res ->> 'resultPath' AS path
FROM jsonb_array_elements(
pgrdf.validate(pgrdf.graph_id('http://example.org/staff'),
pgrdf.graph_id('http://example.org/staff-shapes')) -> 'results') AS res;
-- node | path
-- -------------------------+------------------------
-- http://example.org/dave | http://example.org/ageThe reasoner typed dave as a foaf:Person through the domain of ex:worksOn, so the shape now checks him and finds no age. This is the amber Reason → Validate segment of the chain above.
Next step
For a source larger than your hardware can reason over, carve a slice first with Ingest → Carve → Reason, then validate the slice. For SPARQL-based constraints, see Validate.