diff --git a/docs/generators/shacl.rst b/docs/generators/shacl.rst index 3e88f0090f..4b4b35a7ad 100644 --- a/docs/generators/shacl.rst +++ b/docs/generators/shacl.rst @@ -84,6 +84,103 @@ 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 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. The older form + with a bare ``equals_string: V`` precondition is read the same way, with a + warning. + +Any other rule with one postcondition slot is composed from the operators +its conditions use: ``value_presence``, ``required``, ``equals_string``, +``equals_string_in``, ``minimum_value``, ``maximum_value``, +``range_expression`` (slot conditions on the slot's class) and +``has_member``. + +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``. + ``minimum_value`` and ``maximum_value`` are inclusive bounds, translated + only on slots whose datatype SPARQL compares as a number (``xsd:integer``, + ``decimal``, ``float``, ``double`` and the types derived from them). + +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