Skip to content

SHACL-SPARQL ​

SHACL-SPARQL lets a shape carry a constraint written as a SPARQL SELECT query (W3C SHACL, SPARQL-based constraints). Every solution the query returns for a focus node is a violation.

pgRDF evaluates these in mode => 'pgrdf', which also evaluates every SHACL Core constraint in the same shapes graph. You can mix both kinds in one shapes graph.

Example: no two people share an email address ​

sql
SELECT pgrdf.add_graph('http://example.org/staff');
SELECT pgrdf.add_graph('http://example.org/staff-shapes');

SELECT pgrdf.parse_turtle('
@prefix ex: <http://example.org/> .
ex:ann ex:email "ann@example.org" .
ex:ben ex:email "shared@example.org" .
ex:cat ex:email "shared@example.org" .
', pgrdf.graph_id('http://example.org/staff'));

SELECT pgrdf.parse_turtle('
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://example.org/> .

ex:UniqueEmailShape a sh:NodeShape ;
    sh:targetSubjectsOf ex:email ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Email address is shared with another person" ;
        sh:select """
            PREFIX ex: <http://example.org/>
            SELECT $this ?value WHERE {
              GRAPH <http://example.org/staff> {
                $this  ex:email ?value .
                ?other ex:email ?value .
                FILTER (?other != $this)
              }
            }
        """ ;
    ] .
', pgrdf.graph_id('http://example.org/staff-shapes'));

SELECT jsonb_pretty(pgrdf.validate(pgrdf.graph_id('http://example.org/staff'),
                                   pgrdf.graph_id('http://example.org/staff-shapes'),
                                   mode => 'pgrdf'));
json
{
    "mode": "pgrdf",
    "results": [
        {
            "value": "shared@example.org",
            "focusNode": "http://example.org/ben",
            "resultPath": null,
            "sourceShape": "http://example.org/UniqueEmailShape",
            "resultMessage": "Email address is shared with another person",
            "resultSeverity": "sh:Violation",
            "sourceConstraintComponent": "http://www.w3.org/ns/shacl#SPARQLConstraintComponent"
        },
        {
            "value": "shared@example.org",
            "focusNode": "http://example.org/cat",
            "resultPath": null,
            "sourceShape": "http://example.org/UniqueEmailShape",
            "resultMessage": "Email address is shared with another person",
            "resultSeverity": "sh:Violation",
            "sourceConstraintComponent": "http://www.w3.org/ns/shacl#SPARQLConstraintComponent"
        }
    ],
    "conforms": false,
    "elapsed_ms": 19.588,
    "data_triples": 3,
    "data_graph_id": 8,
    "shapes_triples": 6,
    "shapes_graph_id": 9
}

The GRAPH <http://example.org/staff> { … } wrapper matters. The known limitation below explains why.

How the query is read ​

  • $this is bound, in turn, to each focus node selected by the shape's targets.
  • Each solution is one result. If the query projects ?value, it appears as value in the report.
  • sh:message becomes resultMessage. Without one, resultMessage is null.
  • sourceShape is the node shape's IRI, and sourceConstraintComponent is sh:SPARQLConstraintComponent.
  • The query runs on pgRDF's own SPARQL engine, so the features listed under SPARQL queries are what you can use inside sh:select.

What 'pgrdf' mode evaluates ​

FormEvaluated
sh:sparql with sh:select, on a node shapeYes
SHACL Core constraints in the same shapes graphYes
sh:sparql nested inside a property shape (sh:property [ … sh:sparql … ])No
Custom constraint components (sh:ConstraintComponent with a SPARQL validator such as sh:ask)No

The two unsupported forms are not refused by strict mode. A shape that relies on them passes without being checked. Write those checks as a node-shape sh:select instead. For example, "every email address must be at example.org" as a node-shape constraint:

turtle
ex:EmailDomainShape a sh:NodeShape ;
    sh:targetSubjectsOf ex:email ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "Email must be at example.org" ;
        sh:select """
            PREFIX ex: <http://example.org/>
            SELECT $this ?value WHERE {
              GRAPH <http://example.org/staff> {
                $this ex:email ?value .
                FILTER (!STRENDS(?value, "@example.org"))
              }
            }
        """ ;
    ] .

Known limitation: the query sees every graph ​

In 'pgrdf' mode, the query inside sh:select runs over all graphs in the database, not only the data graph passed to validate. A match in another graph can produce a false violation or duplicate results.

Take the shapes above without the GRAPH wrapper, and add a triple to a different graph that happens to reuse Ann's address:

sql
SELECT pgrdf.add_graph('http://example.org/other');
SELECT pgrdf.parse_turtle('
@prefix ex: <http://example.org/> .
ex:zed ex:email "ann@example.org" .
', pgrdf.graph_id('http://example.org/other'));

Validating http://example.org/staff now also reports http://example.org/ann, because the query found ex:zed in the other graph.

Workaround: wrap the query's pattern in GRAPH <data-graph-iri> { … }, as in the examples on this page. With the wrapper, the same run reports only ben and cat.

Choosing a mode ​

modeEvaluatesUse it for
'native' (default)SHACL CoreShapes graphs without SPARQL constraints. W3C SHACL Core suite 25/25.
'pgrdf'SHACL Core and node-shape sh:sparql / sh:selectShapes graphs with SPARQL-based constraints.
'sparql'An alternative engine; does not evaluate sh:sparqlNot recommended.

Under the default strict => true, a shapes graph containing sh:sparql refuses to run in 'native' mode rather than pass unchecked:

sql
SELECT pgrdf.validate(pgrdf.graph_id('http://example.org/staff'),
                      pgrdf.graph_id('http://example.org/staff-shapes'));
-- ERROR:  validate: unenforced constraint component in shapes graph under mode "native":
--         sh:sparql (SHACL-SPARQL constraint component — use mode 'pgrdf', which evaluates
--         it). This engine does not evaluate it, so a verdict would be meaningless. Re-run
--         with strict => false to validate the remaining constraints anyway (the named
--         component stays unevaluated).

With strict => false the same call returns conforms: true, with the sh:sparql constraint left unevaluated. An unknown mode refuses with SQLSTATE 22023: validate: unknown mode "bogus" (supported: 'native', 'sparql', 'pgrdf'). See Errors and diagnostics.

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