SHACL-SPARQL Rules and Constraints

Stock SHACL Core covers ~95 % of the constraints anyone needs in practice — cardinalities, datatypes, value ranges, property paths. The other 5 % is where SHACL Core runs out of expressiveness: cross-shape conditions, complex logical compositions, "this attribute must equal the sum of those attributes", and so on. SHACL-SPARQL (Advanced Features) closes the gap by letting you embed a SPARQL query inside a shape.

pg_ripple supports sh:SPARQLConstraint (validation), sh:TripleRule (inference), and sh:SPARQLRule (SPARQL CONSTRUCT-based inference, added in v0.79.0).


sh:SPARQLConstraint — custom validation

A sh:SPARQLConstraint runs an ASK or SELECT query for every focus node. If the query returns true (for ASK) or any rows (for SELECT), pg_ripple records a violation.

The classic example is "a person's birth date must be earlier than their death date" — not expressible in pure SHACL Core because it requires comparing two properties of the same node:

SELECT pg_ripple.load_shacl($TTL$
@prefix sh:   <http://www.w3.org/ns/shacl#> .
@prefix ex:   <https://example.org/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .

ex:LifeSpanShape a sh:NodeShape ;
    sh:targetClass foaf:Person ;
    sh:sparql [
        sh:message  "Birth date must be earlier than death date" ;
        sh:select   """
            SELECT $this WHERE {
                $this <https://example.org/birthDate> ?b ;
                      <https://example.org/deathDate> ?d .
                FILTER(?d <= ?b)
            }
        """ ;
    ] .
$TTL$);

-- Validate the whole store.
SELECT focus_node, message FROM pg_ripple.shacl_validate();

Inside the query, the special variable $this is bound to the focus node. The query is evaluated by the same SPARQL engine you would use for any other query, so anything you can write in SPARQL — property paths, FILTER, BIND, sub-SELECT — is fair game inside a constraint.


sh:TripleRule — inference from shapes

A sh:TripleRule adds triples to the store for every focus node that matches the shape. It is the recommended SHACL-AF inference primitive in pg_ripple because it compiles directly to a Datalog rule.

SELECT pg_ripple.load_shacl($TTL$
@prefix sh:   <http://www.w3.org/ns/shacl#> .
@prefix ex:   <https://example.org/> .

ex:AdultRule a sh:NodeShape ;
    sh:targetClass <https://schema.org/Person> ;
    sh:rule [
        a sh:TripleRule ;
        sh:subject   sh:this ;
        sh:predicate ex:isAdult ;
        sh:object    "true"^^<http://www.w3.org/2001/XMLSchema#boolean> ;
    ] .
$TTL$);

-- Apply the rule.
SELECT pg_ripple.shacl_apply_rules();

Triples produced by sh:TripleRule are written with source = 1 (inferred) — they coexist with explicit triples but stay distinguishable.


sh:SPARQLRule — inference from validation shapes

A sh:SPARQLRule runs a CONSTRUCT query whose graph pattern is evaluated for every focus node, and the constructed triples are added to the store. This is essentially "Datalog spelled in SPARQL" — useful when your validation already lives in SHACL and you want to derive new facts on the same data without writing a separate Datalog rule set.

SELECT pg_ripple.load_shacl($TTL$
@prefix sh:   <http://www.w3.org/ns/shacl#> .
@prefix ex:   <https://example.org/> .

ex:AdultRule a sh:NodeShape ;
    sh:targetClass <https://schema.org/Person> ;
    sh:rule [
        a sh:SPARQLRule ;
        sh:construct """
            CONSTRUCT { $this a ex:Adult }
            WHERE     { $this <https://example.org/age> ?age .
                        FILTER(?age >= 18) }
        """ ;
    ] .
$TTL$);

-- Apply the rule.
SELECT pg_ripple.shacl_apply_rules();

Triples constructed by sh:SPARQLRule are written with source = 1 (inferred) — they coexist with explicit triples but stay distinguishable.


When to use SHACL-SPARQL vs Datalog

Both can express custom validation and inference. Pick by audience:

ConcernSHACL-SPARQLDatalog
AudienceData architects who already write SHACLEngineers comfortable with logic programming
ToolingStandard SHACL editors and validatorspg_ripple-specific .pl-style files
ExpressivenessFull SPARQL inside the shapeRecursion, magic sets, lattices, well-founded semantics
PerformanceEach query runs once per focus nodeCompiled to a single SQL INSERT … SELECT per stratum
RecursionLimited — you can recurse manually with property pathsFirst-class — semi-naive evaluation, fixpoint
NegationSPARQL FILTER NOT EXISTSStratified negation; well-founded semantics

Rule of thumb: if the rule fits in one SHACL shape, write it in SHACL-SPARQL. If it is naturally recursive or needs negation, use Datalog.


Performance notes

  • sh:SPARQLConstraint is evaluated per focus node. For shapes whose target matches millions of nodes, pre-filter the target with a tighter sh:targetClass or sh:targetSubjectsOf.
  • sh:SPARQLRule is reapplied on every shacl_apply_rules() call. It is not incremental. For incremental inference, use the Datalog engine.
  • Both run in a single transaction; either everything succeeds or nothing changes.

See also