From 6a78f262cb5b7fc82e8216b64ba17ba1e7e01128 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, 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. --- docs/generators/shacl.rst | 97 +++++++++++++++++++++++++++++++++++++++ docs/schemas/advanced.md | 2 +- 2 files changed, 98 insertions(+), 1 deletion(-) 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