From 170b2fbd8f25f3f4577f94e51424f532c54d1b96 Mon Sep 17 00:00:00 2001 From: Carlo van Driesten Date: Fri, 11 Sep 2026 14:48:35 +0200 Subject: [PATCH] docs(shacl): document rule-to-SHACL-SPARQL constraint generation The SHACL generator translates LinkML rules into sh:sparql constraints, but its documentation did not mention them. Describe the two named patterns and the composed translation with the operators it supports and one constraint per postcondition slot, the semantics shared with the JSON Schema generator (presence, every value versus has_member, value comparison, numeric bounds), inheritance, open_world, --no-emit-rules and the skip warnings, with an example generated by the generator. Replace the dangling "See above for implementation status" in the rules section of advanced.md with links to the JSON Schema and SHACL generator pages. Signed-off-by: jdsika --- docs/generators/shacl.rst | 108 ++++++++++++++++++++++++++++++++++++++ docs/schemas/advanced.md | 2 +- 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/docs/generators/shacl.rst b/docs/generators/shacl.rst index 3e88f0090f..530305adca 100644 --- a/docs/generators/shacl.rst +++ b/docs/generators/shacl.rst @@ -84,6 +84,114 @@ Example Output: shacl:targetClass . +Rule constraints (SHACL-SPARQL) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +LinkML `rules `_ state conditional +constraints ("if slot A holds X, slot B must ..."), which property shapes +cannot express. The generator translates each rule into a +`SHACL-SPARQL constraint `_ +(``sh:sparql``) on the node shape of its class, and of every subclass, since +a rule applies to all members of its class. ``--no-emit-rules`` turns this +off. The metamodel maps ``rules`` to ``sh:rule``, the inference rules of +`SHACL Advanced Features `__, which +add triples rather than report violations; the generator reads a rule as a +constraint instead, as the JSON Schema generator does with ``if`` / ``then``. + +The constraint's query selects each focus node that satisfies the +preconditions and violates the postconditions. ``$this`` is the focus node +(`SHACL ยง5.3.1 `_), +and each result names the postcondition's property and, where there is one, +the offending value (``sh:resultPath``, ``sh:value``). + +Two patterns are recognised first: + +* **Presence implies value**: precondition ``value_presence: PRESENT`` on a + guard slot, and postcondition ``equals_string`` or ``equals_string_in`` on a + target slot. +* **Exclusive value**: precondition ``has_member: {equals_string: V}``, and + postcondition ``maximum_cardinality`` on the same slot. A bare + ``equals_string: V`` precondition applies to every value of the slot, so it + is not this pattern; such a rule is skipped with a warning naming + ``has_member``. + +Any other rule is composed from the operators its conditions use: +``value_presence``, ``required``, ``equals_string``, ``equals_string_in``, +``equals_number``, ``pattern``, ``minimum_value``, ``maximum_value``, the +slot-level ``any_of``, ``all_of``, ``exactly_one_of`` and ``none_of`` over +those value tests, ``range_expression`` (slot conditions on the slot's class) +and ``has_member``. Each postcondition slot gets its own constraint, so a rule +is violated when any of its postconditions is. + +All translations follow the JSON Schema generator's ``if`` / ``then``: + +* **Presence:** ``value_presence`` decides, then ``required``. Otherwise a + precondition requires its slot, a postcondition requires its slot unless + the rule is ``open_world``, and a condition inside a ``range_expression`` + or ``has_member`` does not. +* **Multivalued slots:** a condition holds when *every* value satisfies it. + ``has_member`` holds when *some* value does. +* **Values:** ``equals_string`` and ``equals_string_in`` are compared as + strings on enum and ``xsd:string`` slots, where an enum value with a + ``meaning`` is compared as that IRI. On ``xsd:boolean`` slots they are + compared as booleans and must be ``true``, ``false``, ``1`` or ``0``. + ``equals_number``, ``minimum_value`` and ``maximum_value`` (inclusive) are + translated only on slots whose datatype SPARQL compares as a number + (``xsd:integer``, ``decimal``, ``float``, ``double`` and the types derived + from them). ``pattern`` is matched against the value's string form, as + ``sh:pattern`` matches it. +* **Boolean combinations:** a slot-level ``any_of``, ``all_of``, + ``exactly_one_of`` or ``none_of`` combines the value tests of its members for + each value; ``exactly_one_of`` holds when exactly one member does. + +A rule outside these forms is skipped with a warning that names the rule and +the reason. It is never partially translated. ``deactivated`` rules are +ignored and ``bidirectional`` rules are skipped. For a rule with +``elseconditions``, only the if/then direction is emitted, and a warning +says so. + +Example: + +.. code-block:: yaml + + classes: + Document: + attributes: + review_score: + range: integer + status: + range: Status # an enum: draft, approved, published + rules: + - description: A document scoring 4 or more must be approved or published. + preconditions: + slot_conditions: + review_score: + minimum_value: 4 + postconditions: + slot_conditions: + status: + equals_string_in: [approved, published] + +generates (abridged): + +.. code-block:: turtle + + ex:Document a sh:NodeShape ; + sh:sparql [ a sh:SPARQLConstraint ; + sh:message "A document scoring 4 or more must be approved or published." ; + sh:select """SELECT DISTINCT $this ( AS ?path) ?value WHERE { + FILTER EXISTS { $this ?pre0 . } + FILTER NOT EXISTS { $this ?pre0 . FILTER ( !( COALESCE( isNumeric( ?pre0 ) && ?pre0 >= 4, false ) ) ) } + { FILTER NOT EXISTS { $this ?value . } } + UNION { $this ?value . FILTER ( !( COALESCE( ?value = "approved" || ?value = "published", false ) ) ) } + }""" ] . + +A ``Document`` with a ``review_score`` of 4 or more violates the constraint +if it has no ``status``, or once for each ``status`` other than ``approved`` +or ``published``, which the result reports as ``sh:value``. SHACL processors +that support SHACL-SPARQL, such as ``pyshacl``, validate these constraints. + + Command Line ^^^^^^^^^^^^ diff --git a/docs/schemas/advanced.md b/docs/schemas/advanced.md index c56a808379..1be7ff82ef 100644 --- a/docs/schemas/advanced.md +++ b/docs/schemas/advanced.md @@ -129,7 +129,7 @@ classes: description: USA and territories must have a specific regex pattern for postal codes and phone numbers ``` -See above for implementation status +The [JSON Schema generator](../generators/json-schema) translates rules into `if` / `then` subschemas, and the [SHACL generator](../generators/shacl) into SHACL-SPARQL constraints. ## Defining slots