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