diff --git a/shacl12-rules/migration-guide.md b/shacl12-rules/migration-guide.md new file mode 100644 index 00000000..a3ba3926 --- /dev/null +++ b/shacl12-rules/migration-guide.md @@ -0,0 +1,568 @@ +# Migrating from SHACL-AF Rules to SHACL 1.2 Rules + +## 1. Introduction + +This document describes how to migrate existing SHACL Advanced Features (SHACL-AF) rules +to SHACL 1.2 Rules. It covers both SPARQL Rules (`sh:SPARQLRule`) and Triple Rules +(`sh:TripleRule`) as defined in the SHACL-AF specification, as well as related features +such as Node Expressions and SHACL Functions that are used within rules. + +### Audience + +- **Data modelers/users**: People who have existing SHACL-AF rules in their shapes graphs + and need to rewrite them for SHACL 1.2 implementations. +- **Implementors**: Engine developers migrating their SHACL-AF rule engines to support + SHACL 1.2 Rules. + +### Scope + +This guide covers: +- SPARQL Rules (`sh:SPARQLRule`) +- Triple Rules (`sh:TripleRule`) +- Node Expressions used within rules +- SHACL Functions used within rule expressions + +It does not cover Annotation Properties or Expression Constraints, which are +separate features in SHACL-AF with their own migration paths. Custom Targets are +covered only insofar as they drive rule focus nodes (see +[§6.4](#64-custom-targets--rules--body-patterns)); their use outside rules is out +of scope. + +--- + +## 2. Architectural Differences + +### 2.1 Shape-Attached Rules vs. Standalone Rule Sets + +**SHACL-AF**: Rules are attached to shapes via `sh:rule`. The shape's targets determine +which focus nodes the rules apply to. + +```turtle +ex:PersonShape + a sh:NodeShape ; + sh:targetClass ex:Person ; + sh:rule [ + a sh:SPARQLRule ; + sh:construct "..." ; + ] . +``` + +**SHACL 1.2**: Rules are standalone, grouped in a `srl:RuleSet`. They operate over all +data matching their body patterns. There is no inherent shape attachment. + +``` +PREFIX ex: +RULE { ?x ex:fullName ?name } +WHERE { ?x a ex:Person ; ex:firstName ?f ; ex:lastName ?l . + SET (?name := CONCAT(?f, " ", ?l)) } +``` + +**Migration impact**: You must move targeting logic (class membership, property patterns) +into the rule body as explicit triple patterns. + +### 2.2 Execution Model: Manual Ordering vs. Stratification + +**SHACL-AF**: Rule execution order is controlled manually via `sh:order` on rules and +shapes. Rules with lower values execute first. Visibility of inferred triples between +rules at the same order level is undefined (race conditions are possible). + +**SHACL 1.2**: Ordering is determined automatically via stratification. A +**dependency graph** is built where an edge from `R1` to `R2` means `R1` depends +on `R2` (the head of `R2` could produce triples that `R1`'s body matches). Each +edge is labeled **open** or **closed**: + +- An **open dependency** is an ordinary positive one: the matching triple pattern + in `R1` is a plain triple pattern element. Open dependencies do *not* force a + stratum boundary — the two rules may share a stratum and iterate together. +- A **closed dependency** arises when the matching pattern is inside a `NOT` + block, or when `R1` has an assignment, or when `R1`'s head creates a blank + node. A closed dependency forces `R2` into a strictly earlier stratum so that + `R2` has produced *all* its output before `R1` runs. + +Rules within the same stratum execute to a fixed point (iteratively until no new +triples are produced). The **stratification condition** requires that no +dependency cycle contains a closed dependency; if it does, the spec leaves the +outcome undefined. This guarantees a single well-defined, finite outcome. + +**Migration impact**: Remove `sh:order` annotations. If your rules relied on +explicit ordering for correctness, verify that the stratification produces the +same sequencing. Ordinary positive chains converge in a shared stratum; a `NOT`, +an assignment, or a blank-node-producing head is what introduces a hard stratum +boundary. + +### 2.3 Data Graph Modification vs. Inference Graph Separation + +**SHACL-AF**: The rules engine adds inferred triples to the data graph (logically). +The spec notes implementations may internally use a separate inference graph. + +**SHACL 1.2**: The specification clearly separates the base graph (input) from the +inference graph (output). Combining them is optional and left to users. Rules +evaluate against the "evaluation graph" (base + inferences so far), but output +only contains triples not in the base graph. + +**Migration impact**: If your workflow assumes that inferred triples are automatically +merged back into the data graph, you may need to add an explicit merge step after rule +evaluation. + +### 2.4 Pre-bound `$this` vs. Explicit Variables + +**SHACL-AF**: The variable `$this` is pre-bound to the current focus node determined +by the shape's targets. + +**SHACL 1.2**: No pre-binding. All variables are bound through triple pattern matching +in the rule body. What was `$this` becomes an explicit variable (e.g., `?x`) matched +via triple patterns. + +**Migration impact**: Replace `$this` references with explicit variables and add +corresponding triple patterns to the body. + +--- + +## 3. Vocabulary Mapping + +### 3.1 Classes + +| SHACL-AF | SHACL 1.2 | Notes | +|----------|-----------|-------| +| `sh:SPARQLRule` | `srl:Rule` | Single rule class covers both cases | +| `sh:TripleRule` | `srl:Rule` | Same; triple template structure in head | +| — | `srl:RuleSet` | New; container for rules + data | +| — | `srl:RuleElement` | New; abstract class for body elements | +| — | `srl:TriplePattern` | New; subclass of RuleElement | +| — | `srl:ConditionElement` | New; subclass of RuleElement (FILTER) | +| — | `srl:AssignmentElement` | New; subclass of RuleElement (`SET`) | +| — | `srl:NegationElement` | New; subclass of RuleElement (`NOT`) | + +### 3.2 Properties + +| SHACL-AF | SHACL 1.2 | Notes | +|----------|-----------|-------| +| `sh:rule` (on shape) | `srl:rules` (on RuleSet) | List of rules, not per-shape | +| `sh:construct` | `srl:head` + `srl:body` | `CONSTRUCT` split into head/body | +| `sh:subject` (TripleRule) | `srl:subject` (triple template) | Similar semantics | +| `sh:predicate` (TripleRule) | `srl:predicate` (triple template) | Similar semantics | +| `sh:object` (TripleRule) | `srl:object` (triple template) | Similar semantics | +| `sh:condition` | Body triple patterns + `srl:filter` | Conditions become part of rule body | +| `sh:order` | — | Removed; stratification is automatic | +| `sh:deactivated` | — | Remove rule from set instead | +| `sh:prefixes` | `PREFIX` declarations | SRL has its own prefix mechanism | +| `sh:this` (node expr) | Variable in triple pattern | Explicit binding | +| — | `srl:varName` | New; names variables in RDF syntax | +| — | `srl:not` | New; negation as failure (`NOT`) | +| — | `srl:data` | New; data block triples | +| — | `srl:assign` | New; assignment element (`SET`) | +| — | `srl:assignVar` | New; the variable assigned by an `srl:assign` | +| — | `srl:assignValue` | New; the value/expression of an `srl:assign` | +| — | `srl:filter` | New; `FILTER` equivalent | +| — | `IMPORTS` (SRL text keyword) | New; rule set imports. The RDF-syntax property is not yet defined in the current vocabulary draft | + +The triple-component properties `srl:subject`, `srl:predicate`, and `srl:object` +are used both in `srl:head` (triple templates) and in `srl:body`/`srl:data` +(triple patterns and data triples), not only for the SHACL-AF `sh:subject`/etc. +mapping shown above. + +### 3.3 Namespace Change + +| Spec | Prefix | Namespace | +|------|--------|-----------| +| SHACL-AF | `sh:` | `http://www.w3.org/ns/shacl#` | +| SHACL 1.2 Rules | `srl:` | `http://www.w3.org/ns/shacl-rules#` | +| SHACL 1.2 (expressions) | `sparql:` | `http://www.w3.org/ns/sparql#` | + +--- + +## 4. Syntax Migration + +### 4.1 SPARQL Rules to SRL Text Syntax + +**SHACL-AF SPARQL Rule:** +```turtle +ex:RectangleRulesShape + a sh:NodeShape ; + sh:targetClass ex:Rectangle ; + sh:rule [ + a sh:SPARQLRule ; + sh:prefixes ex: ; + sh:construct """ + CONSTRUCT { + $this ex:area ?area . + } + WHERE { + $this ex:width ?width . + $this ex:height ?height . + BIND (?width * ?height AS ?area) . + } + """ ; + sh:condition ex:RectangleShape ; + ] . +``` + +**SHACL 1.2 SRL:** +``` +PREFIX ex: + +RULE { ?this ex:area ?area } +WHERE { + ?this a ex:Rectangle . + ?this ex:width ?width . + ?this ex:height ?height . + SET (?area := ?width * ?height ) +} +``` + +Key changes: +- `$this` → `?this` (or any variable name); must be bound by body patterns +- `sh:targetClass ex:Rectangle` → `?this a ex:Rectangle .` in body +- `sh:condition ex:RectangleShape` → equivalent constraints as body patterns +- `CONSTRUCT { } WHERE { }` → `RULE { } WHERE { }` +- SPARQL `BIND(expr AS ?v)` → SRL `SET (?v := expr)`; SRL text has no `BIND` +- The assignment makes this rule a **run-once rule** (it has an `srl:assign`) +- Declare prefixes via `PREFIX` keyword, not `sh:prefixes` indirection + +### 4.2 SPARQL Rules to RDF Syntax + +The same rule in `srl:` RDF vocabulary: + +```turtle +PREFIX srl: +PREFIX sparql: +PREFIX ex: + +:areaRuleSet + a srl:RuleSet ; + srl:rules ( + [ + a srl:Rule ; + srl:body ( + [ srl:subject [ srl:varName "this" ] ; + srl:predicate rdf:type ; + srl:object ex:Rectangle ] + [ srl:subject [ srl:varName "this" ] ; + srl:predicate ex:width ; + srl:object [ srl:varName "width" ] ] + [ srl:subject [ srl:varName "this" ] ; + srl:predicate ex:height ; + srl:object [ srl:varName "height" ] ] + [ srl:assign [ + srl:assignVar [ srl:varName "area" ] ; + srl:assignValue [ sparql:multiply ( + [ srl:varName "width" ] + [ srl:varName "height" ] + ) ] + ] ] + ) ; + srl:head ( + [ srl:subject [ srl:varName "this" ] ; + srl:predicate ex:area ; + srl:object [ srl:varName "area" ] ] + ) + ] + ) . +``` + +### 4.3 Triple Rules to SRL Text Syntax + +**SHACL-AF Triple Rule:** +```turtle +ex:Rectangle + a rdfs:Class, sh:NodeShape ; + sh:rule [ + a sh:TripleRule ; + sh:subject sh:this ; + sh:predicate rdf:type ; + sh:object ex:Square ; + sh:condition ex:Rectangle ; + sh:condition [ + sh:property [ + sh:path ex:width ; + sh:equals ex:height ; + ] ; + ] ; + ] . +``` + +**SHACL 1.2 SRL:** +``` +PREFIX ex: + +RULE { ?x a ex:Square } +WHERE { + ?x a ex:Rectangle . + ?x ex:width ?w . + ?x ex:height ?h . + FILTER(?w = ?h) +} +``` + +Key changes: +- `sh:this` → explicit variable `?x` +- `sh:subject`/`sh:predicate`/`sh:object` node expressions → head triple template +- `sh:condition` shapes → body patterns that enforce the same constraints +- `sh:equals` constraint → `FILTER` with equality test + +### 4.4 Triple Rules to RDF Syntax + +```turtle +:squareRuleSet + a srl:RuleSet ; + srl:rules ( + [ + a srl:Rule ; + srl:body ( + [ srl:subject [ srl:varName "x" ] ; + srl:predicate rdf:type ; + srl:object ex:Rectangle ] + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:width ; + srl:object [ srl:varName "w" ] ] + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:height ; + srl:object [ srl:varName "h" ] ] + [ srl:filter [ sparql:equals ( + [ srl:varName "w" ] + [ srl:varName "h" ] + ) ] ] + ) ; + srl:head ( + [ srl:subject [ srl:varName "x" ] ; + srl:predicate rdf:type ; + srl:object ex:Square ] + ) + ] + ) . +``` + +--- + +## 5. New Capabilities in SHACL 1.2 + +These features have no SHACL-AF equivalent. Migrated rules can leverage them +for improved expressiveness. + +### 5.1 Negation as Failure (`NOT`) + +``` +RULE { ?x ex:name ?fullName } +WHERE { + ?x a ex:Person . + NOT { ?x ex:name ?existingName } + ?x ex:givenName ?g ; ex:familyName ?f . + SET (?fullName := CONCAT(?g, " ", ?f)) +} +``` + +Infer a computed name only when no name exists. Previously required workarounds +with SPARQL `NOT EXISTS` inside `sh:construct` strings. Note this rule has both +a negation element and an assignment, so it is a **run-once rule** with closed +dependencies on any rule that could produce `ex:name`. + +### 5.2 Recursion + +``` +RULE { ?x ex:ancestorOf ?y } WHERE { ?x ex:parentOf ?y } +RULE { ?x ex:ancestorOf ?y } WHERE { ?x ex:ancestorOf ?z . ?z ex:ancestorOf ?y } +``` + +SHACL 1.2 Rules can reference their own output. Here the recursion is through an +**open** dependency (a plain triple pattern), so the recursive rule stays in its +stratum and evaluation iterates to a fixed point. SHACL-AF had no defined +behavior for recursive rules. (Recursion through a closed dependency — `NOT`, +assignment, or blank-node head — is disallowed by the stratification condition.) + +### 5.3 Imports (`IMPORTS`) + +``` +PREFIX ex: +IMPORTS + +RULE { ?x ex:derived ?y } WHERE { ... } +``` + +SHACL 1.2 Rule sets can import other rule sets. Imported rules merge into a single +combined rule set before stratification. + +### 5.4 Data Blocks (`DATA`) + +``` +DATA { ex:defaultThreshold ex:value 10 } + +RULE { ?x ex:exceeds true } +WHERE { ?x ex:score ?s . ex:defaultThreshold ex:value ?t . FILTER(?s > ?t) } +``` + +Declare facts inline with the rule set. Useful for configuration constants. + +### 5.5 Shorthand Declarations + +``` +TRANSITIVE(ex:partOf) +SYMMETRIC(ex:relatedTo) +INVERSE(ex:parentOf, ex:childOf) +``` + +Common rule patterns expressed as single declarations. + +--- + +## 6. Features Requiring Different Approaches + +### 6.1 `sh:condition` → Body Patterns + +SHACL-AF `sh:condition` points to a shape that the focus node must conform to. +In SHACL 1.2, replicate the shape's constraints as triple patterns and filters +in the rule body. + +| sh:condition constraint | SRL body equivalent | +|------------------------|---------------------| +| `sh:minCount 1` on path P | `?x P ?val .` (pattern match ensures existence) | +| `sh:maxCount 1` on path P | — (single-valuedness implicit if you bind one var) | +| `sh:datatype xsd:integer` | `FILTER(datatype(?val) = xsd:integer)` | +| `sh:class ex:Foo` | `?val a ex:Foo .` | +| `sh:equals ex:other` | `?x ex:other ?o . FILTER(?val = ?o)` | +| `sh:hasValue ex:v` | `?x P ex:v .` | + +### 6.2 `sh:deactivated` → Remove from Rule Set + +SHACL-AF allows `sh:deactivated true` to disable a rule without deleting it. +In SHACL 1.2, simply remove the rule from the `srl:rules` list or comment it out +in SRL syntax. + +### 6.3 `sh:order` → Automatic Stratification + +SHACL-AF `sh:order` explicitly sequences rules. In SHACL 1.2, stratification +determines order based on dependencies. If rule A's body references predicates +that rule B's head produces, A **depends on** B. + +Whether that dependency creates a stratum boundary depends on its kind: + +- If A matches B's output with a plain triple pattern (an **open dependency**), + A and B may share a stratum and iterate together to a fixed point. There is + **no** guarantee that A lands in a strictly higher stratum than B — and it + does not need one, because the fixed-point iteration lets A re-evaluate as B + produces more triples. +- If A matches B's output inside a `NOT` block, or A has an assignment, or A's + head creates a blank node (a **closed dependency**), then B is placed in a + strictly earlier stratum so it is fully evaluated before A runs. + +If you had rules at the same `sh:order` level that intentionally did NOT see +each other's output, note that SHACL 1.2 rules within the same stratum DO +converge to a fixed point (they iterate). This is a semantic difference. + +### 6.4 Custom Targets + Rules → Body Patterns + +SHACL-AF rules inherit focus nodes from shape targets (including custom +SPARQL-based targets). In SHACL 1.2, move the target's `SELECT` logic into +the rule body as triple patterns. + +**Before (target-driven):** +```turtle +ex:Shape + sh:target [ a sh:SPARQLTarget ; + sh:select "SELECT ?this WHERE { ?this a ex:Person . ?this ex:bornIn ex:USA }" ] ; + sh:rule [ ... ] . +``` + +**After (body pattern):** +``` +RULE { ?x ex:citizenship ex:US } +WHERE { ?x a ex:Person . ?x ex:bornIn ex:USA } +``` + +### 6.5 `$this` Pre-binding → Explicit Variables + +Every occurrence of `$this` in SHACL-AF CONSTRUCT queries becomes a regular +variable matched by the body. + +**Before:** `$this ex:prop ?val` (pre-bound by target) +**After:** `?x ex:prop ?val` where `?x` is bound by other body patterns + +### 6.6 Node Expressions → SRL Expressions + +SHACL-AF node expressions (path expressions, function expressions, filter shape +expressions) used in Triple Rule `sh:object`, etc., map to SRL body patterns plus +a `SET` assignment: + +| Node Expression | SRL Equivalent | +|----------------|----------------| +| `[ sh:path ex:name ]` | Body pattern: `?x ex:name ?result` | +| `[ ex:multiply ( [ sh:path ex:w ] [ sh:path ex:h ] ) ]` | Body patterns + `SET (?result := ex:multiply(?w, ?h))` | +| `[ sh:filterShape ex:S ; sh:nodes [ sh:path ex:p ] ]` | Body patterns with shape constraints as filters | +| `sh:this` | Variable bound elsewhere in body | +| Constant term (e.g., `ex:Square`) | Direct use in head triple template | + +### 6.7 SHACL Functions → Expression Function Calls + +SHACL-AF `sh:SPARQLFunction` declarations define reusable functions callable from +SPARQL or node expressions. In SHACL 1.2, functions are called within SRL expressions +using the same IRI-based function call syntax compatible with SPARQL extensible value +testing. + +**Before (declaration + use in SPARQL Rule):** +```turtle +ex:multiply a sh:SPARQLFunction ; + sh:parameter [ sh:path ex:op1 ] ; + sh:parameter [ sh:path ex:op2 ] ; + sh:select "SELECT ($op1 * $op2 AS ?result) WHERE {}" . +``` +Used as: `BIND(ex:multiply(?w, ?h) AS ?area)` inside `sh:construct`. + +**After (in SRL text syntax):** +``` +SET (?area := ex:multiply(?w, ?h)) +``` + +The function call syntax within expressions is preserved — the change is only +from SPARQL `BIND(expr AS ?v)` to SRL `SET (?v := expr)`. The function +definition mechanism is defined in SHACL 1.2 Node Expressions (separate spec), +not in the rules spec itself. + +--- + +## 7. Implementor Considerations + +### 7.1 Evaluation Model + +SHACL-AF defines a single-pass ordered iteration over shapes and rules. +SHACL 1.2 defines stratified fixed-point evaluation: + +- Build the dependency graph from rule head/body relationships, labeling each + edge **open** or **closed** +- Partition rules into strata (no dependency cycle may contain a closed edge) +- Within a stratum, evaluate **run-once rules** (those with assignments or + blank-node-producing heads) exactly once, then iterate the **general rules** + to a fixed point +- Evaluate each stratum completely and in order before proceeding to the next + +Implementations must detect the **stratification condition** — that no recursive +(cyclic) dependency involves a closed dependency — and report an error if it is +violated. + +### 7.2 Handling Recursive Rules + +SHACL-AF has no defined behavior for recursive rules. SHACL 1.2 explicitly supports +recursion through **open** dependencies within a stratum — rules iterate until no +new triples are produced. Recursion through a **closed** dependency (negation, +assignment, or blank-node-producing head) violates the stratification condition +and is undefined. Implementations need semi-naive evaluation or equivalent to +avoid redundant computation. + +### 7.3 Inference Graph Separation + +The output of SHACL 1.2 rule evaluation is strictly the inference graph — triples +NOT present in the base graph. Implementations must track which triples are new. +This differs from SHACL-AF where the data graph is conceptually modified in place. + +### 7.4 Expression Compatibility + +SHACL 1.2 Rule expressions are compatible with both SPARQL expressions and SHACL +Node Expression list-parameter functions. Implementations supporting SHACL-AF +functions should be able to reuse their expression evaluation logic. + +### 7.5 Two Concrete Syntaxes + +Implementations must handle either or both: +- **SRL text syntax**: `RULE`/`WHERE` grammar (SPARQL-like) +- **RDF syntax**: `srl:` vocabulary in Turtle/other RDF serializations + +Both map to the same abstract syntax. Implementations may choose to support one +and provide translation tooling for the other. diff --git a/shacl12-rules/migration-patterns.md b/shacl12-rules/migration-patterns.md new file mode 100644 index 00000000..d30759f5 --- /dev/null +++ b/shacl12-rules/migration-patterns.md @@ -0,0 +1,689 @@ +# SHACL-AF to SHACL 1.2 Rules: Migration Patterns Cookbook + +A pattern-by-pattern reference for rewriting SHACL-AF rules in SHACL 1.2. +Each pattern is self-contained with before/after examples. + +--- + +## Pattern 1: Simple Property Inference (SPARQL Rule) + +Infer a new property value from existing triples. + +### Before (SHACL-AF) + +```turtle +ex:PersonShape + a sh:NodeShape ; + sh:targetClass ex:Person ; + sh:rule [ + a sh:SPARQLRule ; + sh:prefixes ex: ; + sh:construct """ + CONSTRUCT { + $this ex:label ?label . + } + WHERE { + $this ex:name ?label . + } + """ ; + ] . +``` + +### After (SRL Text Syntax) + +``` +PREFIX ex: + +RULE { ?x ex:label ?label } +WHERE { ?x a ex:Person . ?x ex:name ?label } +``` + +### After (RDF Syntax) + +```turtle +:labelRuleSet + a srl:RuleSet ; + srl:rules ( + [ a srl:Rule ; + srl:body ( + [ srl:subject [ srl:varName "x" ] ; + srl:predicate rdf:type ; + srl:object ex:Person ] + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:name ; + srl:object [ srl:varName "label" ] ] + ) ; + srl:head ( + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:label ; + srl:object [ srl:varName "label" ] ] + ) + ] + ) . +``` + +### Notes + +- `sh:targetClass ex:Person` becomes `?x a ex:Person` in body +- `$this` becomes `?x` (any variable name works) + +--- + +## Pattern 2: Type Classification (Triple Rule + `sh:condition`) + +Classify nodes based on property conditions. + +### Before (SHACL-AF) + +```turtle +ex:Rectangle + a rdfs:Class, sh:NodeShape ; + sh:property [ + sh:path ex:width ; + sh:datatype xsd:integer ; + sh:minCount 1 ; sh:maxCount 1 ; + ] ; + sh:property [ + sh:path ex:height ; + sh:datatype xsd:integer ; + sh:minCount 1 ; sh:maxCount 1 ; + ] ; + sh:rule [ + a sh:TripleRule ; + sh:subject sh:this ; + sh:predicate rdf:type ; + sh:object ex:Square ; + sh:condition ex:Rectangle ; + sh:condition [ + sh:property [ + sh:path ex:width ; + sh:equals ex:height ; + ] ; + ] ; + ] . +``` + +### After (SRL Text Syntax) + +``` +PREFIX ex: + +RULE { ?x a ex:Square } +WHERE { + ?x a ex:Rectangle . + ?x ex:width ?w . + ?x ex:height ?h . + FILTER(?w = ?h) +} +``` + +### After (RDF Syntax) + +```turtle +:squareRuleSet + a srl:RuleSet ; + srl:rules ( + [ a srl:Rule ; + srl:body ( + [ srl:subject [ srl:varName "x" ] ; + srl:predicate rdf:type ; + srl:object ex:Rectangle ] + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:width ; + srl:object [ srl:varName "w" ] ] + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:height ; + srl:object [ srl:varName "h" ] ] + [ srl:filter [ sparql:equals ( + [ srl:varName "w" ] + [ srl:varName "h" ] + ) ] ] + ) ; + srl:head ( + [ srl:subject [ srl:varName "x" ] ; + srl:predicate rdf:type ; + srl:object ex:Square ] + ) + ] + ) . +``` + +### Notes + +- `sh:condition ex:Rectangle` → class check and property existence in body +- `sh:equals ex:height` → FILTER equality test +- Datatype/cardinality constraints from sh:condition shape → implicit through + pattern matching (binding `?w` and `?h` ensures at least one value exists) + +--- + +## Pattern 3: Computed Values (SPARQL Rule + `BIND`) + +Compute derived values using arithmetic or string operations. + +### Before (SHACL-AF) + +```turtle +ex:RectangleRulesShape + a sh:NodeShape ; + sh:targetClass ex:Rectangle ; + sh:rule [ + a sh:SPARQLRule ; + sh:prefixes ex: ; + sh:construct """ + CONSTRUCT { + $this ex:area ?area . + } + WHERE { + $this ex:width ?width . + $this ex:height ?height . + BIND(?width * ?height AS ?area) . + } + """ ; + sh:condition ex:RectangleShape ; + ] . +``` + +### After (SRL Text Syntax) + +``` +PREFIX ex: + +RULE { ?x ex:area ?area } +WHERE { + ?x a ex:Rectangle . + ?x ex:width ?width . + ?x ex:height ?height . + SET (?area := ?width * ?height) +} +``` + +### After (RDF Syntax) + +```turtle +:areaRuleSet + a srl:RuleSet ; + srl:rules ( + [ a srl:Rule ; + srl:body ( + [ srl:subject [ srl:varName "x" ] ; + srl:predicate rdf:type ; + srl:object ex:Rectangle ] + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:width ; + srl:object [ srl:varName "width" ] ] + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:height ; + srl:object [ srl:varName "height" ] ] + [ srl:assign [ + srl:assignVar [ srl:varName "area" ] ; + srl:assignValue [ sparql:multiply ( + [ srl:varName "width" ] + [ srl:varName "height" ] + ) ] + ] ] + ) ; + srl:head ( + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:area ; + srl:object [ srl:varName "area" ] ] + ) + ] + ) . +``` + +### Notes + +- SHACL-AF SPARQL `BIND(expr AS ?v)` becomes SRL `SET (?v := expr)` in the + text syntax. SRL has no `BIND` keyword; assignment is written with `SET`. + Error handling also differs: if the expression errors, `SET` rejects the + current solution mapping rather than leaving the variable unbound. +- `sh:condition ex:RectangleShape` → body patterns that enforce same constraints +- RDF syntax uses `srl:assign` + `srl:assignVar` + `srl:assignValue` +- An assignment makes the rule a **run-once rule**: it is evaluated exactly + once at the start of its stratum, after the rules it depends on and before + the rules that depend on it + +--- + +## Pattern 4: Conditional Inference (`sh:condition` shapes) + +Apply rules only to nodes conforming to specific shapes. + +### Before (SHACL-AF) + +```turtle +ex:PersonRulesShape + a sh:NodeShape ; + sh:targetClass ex:Person ; + sh:rule [ + a sh:SPARQLRule ; + sh:prefixes ex: ; + sh:construct """ + CONSTRUCT { $this ex:status "adult" } + WHERE { $this ex:age ?age . FILTER(?age >= 18) } + """ ; + sh:condition [ + sh:property [ + sh:path ex:age ; + sh:minCount 1 ; + sh:datatype xsd:integer ; + ] ; + ] ; + ] . +``` + +### After (SRL Text Syntax) + +``` +PREFIX ex: + +RULE { ?x ex:status "adult" } +WHERE { + ?x a ex:Person . + ?x ex:age ?age . + FILTER(datatype(?age) = xsd:integer) + FILTER(?age >= 18) +} +``` + +### After (RDF Syntax) + +```turtle +:adultRuleSet + a srl:RuleSet ; + srl:rules ( + [ a srl:Rule ; + srl:body ( + [ srl:subject [ srl:varName "x" ] ; + srl:predicate rdf:type ; + srl:object ex:Person ] + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:age ; + srl:object [ srl:varName "age" ] ] + [ srl:filter [ sparql:equals ( + [ sparql:datatype ( [ srl:varName "age" ] ) ] + xsd:integer + ) ] ] + [ srl:filter [ sparql:greater-than-or-equal ( + [ srl:varName "age" ] + 18 + ) ] ] + ) ; + srl:head ( + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:status ; + srl:object "adult" ] + ) + ] + ) . +``` + +### Notes + +- `sh:minCount 1` → body pattern match (binding ensures value exists) +- `sh:datatype xsd:integer` → `FILTER(datatype(?age) = xsd:integer)` +- Multiple conditions stack as multiple FILTER elements +- In practice, if your data is clean, the datatype filter may be unnecessary + +--- + +## Pattern 5: Ordered/Chained Rules (`sh:order`) + +Rules that must execute in sequence, where later rules depend on earlier results. + +### Before (SHACL-AF) + +```turtle +ex:PersonRulesShape + a sh:NodeShape ; + sh:targetClass ex:Person ; + sh:rule [ + a sh:SPARQLRule ; + sh:order 1 ; + sh:prefixes ex: ; + sh:construct """ + CONSTRUCT { $this ex:uncle ?uncle } + WHERE { + $this ex:parent ?parent . + ?parent ex:sibling ?uncle . + ?uncle ex:gender ex:male . + } + """ ; + ] ; + sh:rule [ + a sh:SPARQLRule ; + sh:order 2 ; + sh:prefixes ex: ; + sh:construct """ + CONSTRUCT { $this ex:cousin ?cousin } + WHERE { + $this ex:uncle ?uncle . + ?cousin ex:parent ?uncle . + } + """ ; + ] . +``` + +### After (SRL Text Syntax) + +``` +PREFIX ex: + +RULE { ?x ex:uncle ?uncle } +WHERE { + ?x a ex:Person . + ?x ex:parent ?parent . + ?parent ex:sibling ?uncle . + ?uncle ex:gender ex:male +} + +RULE { ?x ex:cousin ?cousin } +WHERE { + ?x a ex:Person . + ?x ex:uncle ?uncle . + ?cousin ex:parent ?uncle +} +``` + +### Notes + +- No `sh:order` needed. The cousin rule references `ex:uncle` in its body, and + the uncle rule produces `ex:uncle` in its head, so the cousin rule **depends + on** the uncle rule. +- This is an **open dependency**: the cousin rule's `ex:uncle` triple pattern is + a plain triple pattern element (not inside `NOT`, no assignment, no blank node + in the head). Open dependencies do **not** force separate strata — both rules + sit in the **same** stratum and iterate together to a single fixed point. The + iteration guarantees the cousin rule sees every `ex:uncle` triple the uncle + rule eventually produces. +- A *closed* dependency (negation, assignment, or a blank node in the head) is + what forces the depended-on rule into a strictly earlier stratum. Ordinary + positive chaining like this does not. + +--- + +## Pattern 6: Function-Based Computation (Node Expressions + SHACL Functions) + +Using SHACL Functions within Triple Rules via node expressions. + +### Before (SHACL-AF) + +```turtle +ex:multiply + a sh:SPARQLFunction ; + sh:parameter [ sh:path ex:op1 ; sh:datatype xsd:integer ] ; + sh:parameter [ sh:path ex:op2 ; sh:datatype xsd:integer ] ; + sh:returnType xsd:integer ; + sh:select "SELECT ($op1 * $op2 AS ?result) WHERE {}" . + +ex:RectangleRulesShape + a sh:NodeShape ; + sh:targetClass ex:Rectangle ; + sh:rule [ + a sh:TripleRule ; + sh:subject sh:this ; + sh:predicate ex:area ; + sh:object [ + ex:multiply ( [ sh:path ex:width ] [ sh:path ex:height ] ) ; + ] ; + sh:condition ex:RectangleShape ; + ] . +``` + +### After (SRL Text Syntax) + +``` +PREFIX ex: + +RULE { ?x ex:area ?area } +WHERE { + ?x a ex:Rectangle . + ?x ex:width ?w . + ?x ex:height ?h . + SET (?area := ex:multiply(?w, ?h)) +} +``` + +### After (RDF Syntax) + +```turtle +:areaRuleSet + a srl:RuleSet ; + srl:rules ( + [ a srl:Rule ; + srl:body ( + [ srl:subject [ srl:varName "x" ] ; + srl:predicate rdf:type ; + srl:object ex:Rectangle ] + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:width ; + srl:object [ srl:varName "w" ] ] + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:height ; + srl:object [ srl:varName "h" ] ] + [ srl:assign [ + srl:assignVar [ srl:varName "area" ] ; + srl:assignValue [ ex:multiply ( + [ srl:varName "w" ] + [ srl:varName "h" ] + ) ] + ] ] + ) ; + srl:head ( + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:area ; + srl:object [ srl:varName "area" ] ] + ) + ] + ) . +``` + +### Notes + +- Node expression `[ ex:multiply ( [ sh:path ex:width ] [ sh:path ex:height ] ) ]` + decomposes into: path expressions → body patterns, function call → `SET` assignment +- Function IRI (`ex:multiply`) used directly in the `SET` expression — same calling convention +- Because the rule has an assignment, it is a **run-once rule** (see Pattern 3) +- Function definition mechanism is in SHACL 1.2 Node Expressions specification, not rules specification + +--- + +## Pattern 7: Path-Based Triple Derivation (Triple Rule + Path Expressions) + +Using property paths in Triple Rules to navigate graph structure. + +### Before (SHACL-AF) + +```turtle +ex:OrgShape + a sh:NodeShape ; + sh:targetClass ex:Organization ; + sh:rule [ + a sh:TripleRule ; + sh:subject sh:this ; + sh:predicate ex:memberName ; + sh:object [ + sh:path ( ex:member ex:name ) ; + ] ; + ] . +``` + +### After (SRL Text Syntax) + +``` +PREFIX ex: + +RULE { ?org ex:memberName ?name } +WHERE { + ?org a ex:Organization . + ?org ex:member/ex:name ?name +} +``` + +### After (RDF Syntax) + +```turtle +:memberNameRuleSet + a srl:RuleSet ; + srl:rules ( + [ a srl:Rule ; + srl:body ( + [ srl:subject [ srl:varName "org" ] ; + srl:predicate rdf:type ; + srl:object ex:Organization ] + [ srl:subject [ srl:varName "org" ] ; + srl:predicate ex:member ; + srl:object [ srl:varName "m" ] ] + [ srl:subject [ srl:varName "m" ] ; + srl:predicate ex:name ; + srl:object [ srl:varName "name" ] ] + ) ; + srl:head ( + [ srl:subject [ srl:varName "org" ] ; + srl:predicate ex:memberName ; + srl:object [ srl:varName "name" ] ] + ) + ] + ) . +``` + +### Notes + +- SHACL sequence paths `( ex:member ex:name )` → SRL path syntax `ex:member/ex:name` + (SRL supports SPARQL property paths in body) +- In RDF syntax, multi-step paths decompose into separate triple patterns with + intermediate variables + +--- + +## Pattern 8: Multi-Triple Output (SPARQL `CONSTRUCT` with Multiple Patterns) + +Rules that produce multiple triples per match. + +### Before (SHACL-AF) + +```turtle +ex:PersonShape + a sh:NodeShape ; + sh:targetClass ex:Person ; + sh:rule [ + a sh:SPARQLRule ; + sh:prefixes ex: ; + sh:construct """ + CONSTRUCT { + $this ex:fullName ?full . + $this ex:initials ?init . + } + WHERE { + $this ex:firstName ?f . + $this ex:lastName ?l . + BIND(CONCAT(?f, " ", ?l) AS ?full) . + BIND(CONCAT(SUBSTR(?f, 1, 1), SUBSTR(?l, 1, 1)) AS ?init) . + } + """ ; + ] . +``` + +### After (SRL Text Syntax) + +``` +PREFIX ex: + +RULE { + ?x ex:fullName ?full . + ?x ex:initials ?init +} +WHERE { + ?x a ex:Person . + ?x ex:firstName ?f . + ?x ex:lastName ?l . + SET (?full := CONCAT(?f, " ", ?l)) + SET (?init := CONCAT(SUBSTR(?f, 1, 1), SUBSTR(?l, 1, 1))) +} +``` + +### After (RDF Syntax) + +```turtle +:nameRuleSet + a srl:RuleSet ; + srl:rules ( + [ a srl:Rule ; + srl:body ( + [ srl:subject [ srl:varName "x" ] ; + srl:predicate rdf:type ; + srl:object ex:Person ] + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:firstName ; + srl:object [ srl:varName "f" ] ] + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:lastName ; + srl:object [ srl:varName "l" ] ] + [ srl:assign [ + srl:assignVar [ srl:varName "full" ] ; + srl:assignValue [ sparql:concat ( + [ srl:varName "f" ] + " " + [ srl:varName "l" ] + ) ] + ] ] + [ srl:assign [ + srl:assignVar [ srl:varName "init" ] ; + srl:assignValue [ sparql:concat ( + [ sparql:substr ( [ srl:varName "f" ] 1 1 ) ] + [ sparql:substr ( [ srl:varName "l" ] 1 1 ) ] + ) ] + ] ] + ) ; + srl:head ( + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:fullName ; + srl:object [ srl:varName "full" ] ] + [ srl:subject [ srl:varName "x" ] ; + srl:predicate ex:initials ; + srl:object [ srl:varName "init" ] ] + ) + ] + ) . +``` + +### Notes + +- Multiple triple templates in the head → multiple inferred triples per match +- Each head triple template is a separate element in the `srl:head` list +- In SRL text syntax, SPARQL built-in functions are written with their SPARQL + keyword spelling (`CONCAT`, `SUBSTR`); in the RDF syntax they are IRIs in the + `sparql:` namespace (`sparql:concat`, `sparql:substr`) +- Two assignments make this a **run-once rule**; it is evaluated once per stratum + +--- + +## Quick Reference: Migration Checklist + +For each SHACL-AF rule, follow these steps: + +1. **Identify target logic** — what `sh:targetClass`, `sh:targetNode`, or `sh:target` + selects. Convert to body triple patterns. + +2. **Extract conditions** — what `sh:condition` shapes enforce. Convert constraints + to body patterns + filters. + +3. **Map `$this`** — replace with explicit variable, ensure it's bound by body patterns. + +4. **Convert construct/triple template** — `CONSTRUCT` body → SRL body; `CONSTRUCT` head + → SRL head. Triple Rule properties → head triple template. + +5. **Remove ordering** — delete `sh:order`. Verify dependency-based stratification + preserves intended semantics. Remember: ordinary (open) dependencies iterate + together in one stratum; only closed dependencies (negation, assignment, + blank node in head) force a strictly earlier stratum. + +6. **Decompose node expressions** — path expressions become body patterns; function + expressions become `SET` assignments (SRL text syntax has no `BIND`); filter + shapes become body constraints. + +7. **Group into rule set** — collect related rules into an `srl:RuleSet`. + +8. **Leverage new features** — consider whether negation, recursion, or `TRANSITIVE` + declarations simplify your rules.