Skip to content

Validation

pgrdf.validate checks a data graph against a SHACL shapes graph and returns a W3C-shaped validation report as JSONB. It never writes. The code is in src/validation/shacl.rs (the Core pipeline and the dispatcher) and src/validation/pgrdf_sparql.rs (the SHACL-SPARQL evaluator). The user guide is Validation.

sql
pgrdf.validate(data_graph_id   BIGINT,
               shapes_graph_id BIGINT,
               mode            TEXT    DEFAULT 'native',
               strict          BOOLEAN DEFAULT true) → JSONB

Modes

ModeEngineEvaluates
'native' (default)rudof's shacl crate, native engineSHACL Core
'pgrdf'the native Core engine plus pgRDF's SHACL-SPARQL evaluator; the two reports are mergedSHACL Core and sh:sparql / sh:select constraints
'sparql'rudof's SPARQL engineSHACL Core only, and not sh:minCount / sh:maxCount; does not evaluate sh:sparql. Not recommended.

An unknown mode is refused before any work, with 22023:

text
ERROR:  validate: unknown mode "endpoint" (supported: 'native', 'sparql', 'pgrdf')

Use 'native' for Core-only shapes graphs and 'pgrdf' whenever a shapes graph contains SHACL-SPARQL constraints.

Strict mode

strict => true (the default) refuses a validation whose verdict would be meaningless.

  • A shapes graph that targets nothing. If none of sh:targetClass, sh:targetNode, sh:targetSubjectsOf and sh:targetObjectsOf occurs, and no node is both sh:NodeShape and rdfs:Class (implicit class targeting), no focus node can be selected, and every data graph would "conform". A wrong or empty graph id produces exactly this case.
  • A constraint component the mode does not evaluate. sh:sparql under 'native' or 'sparql', and sh:minCount / sh:maxCount under 'sparql'. Otherwise the component would contribute neither a violation nor an error, and conforms: true would hide it.
text
ERROR:  validate: unenforced constraint component in shapes graph under mode "native":
        sh:sparql (SHACL-SPARQL constraint component — use mode 'pgrdf', which evaluates it). …

The predicates used for these checks are read from _pgrdf_quads, not scanned out of serialised text. strict => false skips both checks and validates whatever it can; the unevaluated components stay unevaluated. Passing the same graph as data and shapes raises a WARNING, because common targets then select the shape declarations themselves as focus nodes.

Report

json
{
  "conforms": false,
  "results": [
    {
      "focusNode": "http://example.org/alice",
      "resultPath": "http://example.org/age",
      "value": null,
      "sourceShape": "_:c1eff7dd8f08c03dae4be24cc25c7798",
      "sourceConstraintComponent": "http://www.w3.org/ns/shacl#MinCountConstraintComponent",
      "resultSeverity": "sh:Violation",
      "resultMessage": "MinCount(1) not satisfied"
    }
  ],
  "mode": "native",
  "data_graph_id": 46,
  "shapes_graph_id": 47,
  "data_triples": 5,
  "shapes_triples": 10,
  "elapsed_ms": 1.41
}

conforms is true exactly when results is empty. Severities are normalised to sh:Violation, sh:Warning, sh:Info (and so on), and terms are rendered as IRIs, blank-node labels or literals. The report shape is the same for every mode.

The Core pipeline

For 'native', and the Core half of 'pgrdf':

  1. Read back. Both graphs are read from _pgrdf_quads joined to the dictionary. Asserted and inferred rows are included, so materialize followed by validate checks the entailed closure.
  2. Serialise. Each graph is written to N-Triples in memory with oxttl's serialiser.
  3. Parse. rudof's InMemoryGraph parses each N-Triples text.
  4. Compile. The shapes graph is compiled into rudof's shape IR.
  5. Validate. rudof's GraphValidation runs the selected engine.
  6. Map. Each result becomes a JSON object in the shape above.

Everything runs inside the calling backend and transaction: there is no external endpoint and no file I/O. Blank nodes in the report carry the labels minted during read-back, so they are stable within one call but not across calls.

The SHACL-SPARQL evaluator

Under 'pgrdf', after the Core pass, run_pgrdf_sparql:

  1. walks the compiled shapes for sh:sparql constraints and their sh:select queries;
  2. resolves each shape's targets against the data graph (node, class, implicit class, subjects-of, objects-of) into a set of focus nodes;
  3. for each focus node, substitutes $this into the sh:select text and runs the query through pgrdf.sparql, so it uses the hexastore indexes and the plan cache rather than an in-memory copy;
  4. turns each returned row into a result with sourceConstraintComponent = sh:SPARQLConstraintComponent, taking sh:message as the message.

The Core and SPARQL results are merged into one report.

Known limitation. The query inside sh:select runs through pgrdf.sparql, which sees all graphs, not only the data graph. A pattern that also matches triples in other graphs produces false positives or duplicate results. Until this is scoped automatically, wrap the pattern in GRAPH <data-graph-iri> { … }:

sql
SELECT pgrdf.add_graph('http://example.org/data');
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/data'));

SELECT pgrdf.add_graph('http://example.org/name-shapes');
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/> .
ex:NameShape a sh:NodeShape ;
  sh:targetClass foaf:Person ;
  sh:sparql [
    sh:message "Name is shorter than four characters" ;
    sh:select """SELECT $this ?n WHERE {
                   GRAPH <http://example.org/data> { $this <http://xmlns.com/foaf/0.1/name> ?n }
                   FILTER(STRLEN(?n) < 4) }""" ] .
', pgrdf.graph_id('http://example.org/name-shapes'));

SELECT pgrdf.validate(pgrdf.graph_id('http://example.org/data'),
                      pgrdf.graph_id('http://example.org/name-shapes'),
                      'pgrdf');
-- conforms: false; one result, focusNode http://example.org/bob,
-- sourceConstraintComponent sh:SPARQLConstraintComponent

The same call with the default 'native' mode is refused by strict mode, because 'native' does not evaluate sh:sparql.

Scope

  • SHACL Core node and property shapes with the standard Core constraint components (cardinality, value type, value range, string, property pair, logical and shape-based).
  • SHACL-SPARQL sh:sparql / sh:select constraints, under 'pgrdf'.
  • Not supported: JavaScript constraints (sh:js), SHACL Advanced Features such as sh:rule, and RDF-star quoted triples as focus nodes.

Which components are actually enforced on a given build is measured, not assumed, by the capability harness below.

Dependencies

  • shacl 0.3.2 and rudof_rdf 0.3 from the rudof project provide the Core engine, the shape compiler and the in-memory graph.
  • shacl enables RDF 1.2 support in oxrdf unconditionally. The reasonable fork patched in Cargo.toml exists so both crates can share one oxrdf build.

Conformance gates and tests

  • W3C SHACL Core suite (tests/w3c-shacl/). A vendored, hermetic subset of the W3C data-shapes Core tests, run by just test-shacl-manifest: 25 of 25 pass. The gate compares conforms, not the number of results, because the read-back relabels blank nodes and can shift a violation count by one without changing the verdict. Each fixture keeps the unmodified W3C source (<name>.w3c.ttl) beside the file the harness loads.
  • SHACL-SPARQL fixtures. just test-shacl-manifest --pgrdf runs them through 'pgrdf' against the W3C expected verdicts; --sparql checks that the 'sparql' dispatch keeps its documented contract. Both run in CI.
  • Capability document (tests/shacl-capability/). For each constraint component and target type, a shapes file, a violating data file and a control data file. A component counts as enforced only when the violating case reports conforms: false and the control reports conforms: true. The result is written to CAPABILITY.json.
  • Regression files.70-validate-stub.sql (report shape and degenerate cases), 71-shacl-real.sql (a failing focus node), and 122-shacl-modes.sql (modes, unknown mode, validation of a materialised graph).
  • #[pg_test]s in shacl.rs cover conforming and failing graphs, unknown graphs, the mode default and refusals, strict-mode refusals and validation after materialisation.

See Testing for how these fit into CI.

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