From 6e6f7a387d9cfd516ffa6e115752070cb63da710 Mon Sep 17 00:00:00 2001 From: Simon Steyskal Date: Wed, 17 Jun 2026 09:01:33 +0200 Subject: [PATCH 1/2] feat: Add SHACL-AF to SHACL 1.2 migration guide and patterns --- shacl12-rules/migration-guide.md | 515 +++++++++++++++++++++ shacl12-rules/migration-patterns.md | 668 ++++++++++++++++++++++++++++ 2 files changed, 1183 insertions(+) create mode 100644 shacl12-rules/migration-guide.md create mode 100644 shacl12-rules/migration-patterns.md diff --git a/shacl12-rules/migration-guide.md b/shacl12-rules/migration-guide.md new file mode 100644 index 00000000..d8b26ec2 --- /dev/null +++ b/shacl12-rules/migration-guide.md @@ -0,0 +1,515 @@ +# 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 Custom Targets, Annotation Properties, or Expression Constraints, +which are separate features in SHACL-AF with their own migration paths. + +--- + +## 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 . + BIND(concat(?f, " ", ?l) AS ?name) } +``` + +**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. The dependency +graph between rules determines which rules must complete before others begin. Rules +within the same stratum execute to a fixed point (iteratively until no new triples). +This guarantees a single well-defined outcome. + +**Migration impact**: Remove `sh:order` annotations. If your rules relied on explicit +ordering for correctness, verify that the stratification algorithm produces the same +sequencing. In most cases it will — rules that consume output of other rules naturally +end up in higher strata. + +### 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 (BIND) | +| — | `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 | +| — | `srl:data` | New; data block triples | +| — | `srl:assign` | New; `BIND` equivalent | +| — | `srl:filter` | New; `FILTER` equivalent | + +### 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 . + BIND(?width * ?height AS ?area) +} +``` + +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 { }` +- 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 . + BIND(concat(?g, " ", ?f) AS ?fullName) +} +``` + +Infer a computed name only when no name exists. Previously required workarounds +with SPARQL `NOT EXISTS` inside `sh:construct` strings. + +### 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. Evaluation iterates to a fixed point +within each stratum. SHACL-AF had no defined behavior for recursive rules. + +### 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 ends up in a higher stratum than B — automatically. + +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 + `BIND`: + +| 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 + `BIND(ex:multiply(?w, ?h) AS ?result)` | +| `[ 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):** +``` +BIND(ex:multiply(?w, ?h) AS ?area) +``` + +The function call syntax within expressions is preserved. However, 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 dependency graph from rule head/body relationships +- Partition rules into strata (no negative cycles allowed) +- Evaluate each stratum to fixed point before proceeding to next + +Implementations must detect the stratification condition (no recursive negative +dependencies) and report an error if violated. + +### 7.2 Handling Recursive Rules + +SHACL-AF has no defined behavior for recursive rules. SHACL 1.2 explicitly supports +positive recursion within a stratum — rules iterate until no new triples are produced. +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..244c161d --- /dev/null +++ b/shacl12-rules/migration-patterns.md @@ -0,0 +1,668 @@ +# 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 . + SET (?area := ?width * ?height ) . + } + """ ; + 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 . + BIND(?width * ?height AS ?area) +} +``` + +### 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 + +- BIND syntax identical between SPARQL and SRL +- `sh:condition ex:RectangleShape` → body patterns that enforce same constraints +- RDF syntax uses `srl:assign` + `srl:assignVar` + `srl:assignValue` + +--- + +## 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:greaterThanOrEqual ( + [ 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. Stratification automatically + places the uncle rule in a lower (earlier) stratum. +- Both rules run to fixed point within their respective strata. + +--- + +## 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 . + BIND(ex:multiply(?w, ?h) AS ?area) +} +``` + +### 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 → `BIND` expression +- Function IRI (`ex:multiply`) used directly in `BIND` — same calling convention +- 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 . + BIND(concat(?f, " ", ?l) AS ?full) + BIND(concat(substr(?f, 1, 1), substr(?l, 1, 1)) AS ?init) +} +``` + +### 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 +- SPARQL built-in functions (CONCAT, SUBSTR) available via `sparql:` namespace + +--- + +## 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. + +6. **Decompose node expressions** — path expressions become body patterns; function + expressions become `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. From 25067dfc1469071b20c28f2023c7cae6d736edfd Mon Sep 17 00:00:00 2001 From: Simon Steyskal Date: Wed, 17 Jun 2026 10:31:34 +0200 Subject: [PATCH 2/2] fix: Update migration patterns for SHACL 1.2 syntax and clarify rule dependencies --- shacl12-rules/migration-guide.md | 149 +++++++++++++++++++--------- shacl12-rules/migration-patterns.md | 69 ++++++++----- 2 files changed, 146 insertions(+), 72 deletions(-) diff --git a/shacl12-rules/migration-guide.md b/shacl12-rules/migration-guide.md index d8b26ec2..a3ba3926 100644 --- a/shacl12-rules/migration-guide.md +++ b/shacl12-rules/migration-guide.md @@ -22,8 +22,11 @@ This guide covers: - Node Expressions used within rules - SHACL Functions used within rule expressions -It does not cover Custom Targets, Annotation Properties, or Expression Constraints, -which are separate features in SHACL-AF with their own migration paths. +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. --- @@ -51,7 +54,7 @@ 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 . - BIND(concat(?f, " ", ?l) AS ?name) } + SET (?name := CONCAT(?f, " ", ?l)) } ``` **Migration impact**: You must move targeting logic (class membership, property patterns) @@ -61,31 +64,45 @@ into the rule body as explicit triple patterns. **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. The dependency -graph between rules determines which rules must complete before others begin. Rules -within the same stratum execute to a fixed point (iteratively until no new triples). -This guarantees a single well-defined outcome. - -**Migration impact**: Remove `sh:order` annotations. If your rules relied on explicit -ordering for correctness, verify that the stratification algorithm produces the same -sequencing. In most cases it will — rules that consume output of other rules naturally -end up in higher strata. +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. +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. +**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 @@ -113,28 +130,36 @@ corresponding triple patterns to the body. | — | `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 (BIND) | -| — | `srl:NegationElement` | New; subclass of RuleElement (NOT) | +| — | `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: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: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 | +| — | `srl:not` | New; negation as failure (`NOT`) | | — | `srl:data` | New; data block triples | -| — | `srl:assign` | New; `BIND` equivalent | -| — | `srl:filter` | New; `FILTER` equivalent | +| — | `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 @@ -181,7 +206,7 @@ WHERE { ?this a ex:Rectangle . ?this ex:width ?width . ?this ex:height ?height . - BIND(?width * ?height AS ?area) + SET (?area := ?width * ?height ) } ``` @@ -190,7 +215,9 @@ Key changes: - `sh:targetClass ex:Rectangle` → `?this a ex:Rectangle .` in body - `sh:condition ex:RectangleShape` → equivalent constraints as body patterns - `CONSTRUCT { } WHERE { }` → `RULE { } WHERE { }` -- Declare prefixes via `PREFIX` keyword, not `sh:prefixes` indirection +- 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 @@ -320,12 +347,14 @@ WHERE { ?x a ex:Person . NOT { ?x ex:name ?existingName } ?x ex:givenName ?g ; ex:familyName ?f . - BIND(concat(?g, " ", ?f) AS ?fullName) + 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. +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 @@ -334,8 +363,11 @@ 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. Evaluation iterates to a fixed point -within each stratum. SHACL-AF had no defined behavior for recursive rules. +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`) @@ -346,8 +378,8 @@ 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. +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`) @@ -399,7 +431,18 @@ in SRL syntax. 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 ends up in a higher stratum than B — automatically. +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 @@ -408,7 +451,7 @@ 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 +SPARQL-based targets). In SHACL 1.2, move the target's `SELECT` logic into the rule body as triple patterns. **Before (target-driven):** @@ -436,12 +479,13 @@ variable matched by the body. ### 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 + `BIND`: +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 + `BIND(ex:multiply(?w, ?h) AS ?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 | @@ -462,12 +506,13 @@ ex:multiply a sh:SPARQLFunction ; ``` Used as: `BIND(ex:multiply(?w, ?h) AS ?area)` inside `sh:construct`. -**After (in SRL):** +**After (in SRL text syntax):** ``` -BIND(ex:multiply(?w, ?h) AS ?area) +SET (?area := ex:multiply(?w, ?h)) ``` -The function call syntax within expressions is preserved. However, the function +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. @@ -480,18 +525,26 @@ not in the rules spec itself. SHACL-AF defines a single-pass ordered iteration over shapes and rules. SHACL 1.2 defines stratified fixed-point evaluation: -- Build dependency graph from rule head/body relationships -- Partition rules into strata (no negative cycles allowed) -- Evaluate each stratum to fixed point before proceeding to next +- 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 (no recursive negative -dependencies) and report an error if violated. +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 -positive recursion within a stratum — rules iterate until no new triples are produced. -Implementations need semi-naive evaluation or equivalent to avoid redundant computation. +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 @@ -508,7 +561,7 @@ 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) +- **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 diff --git a/shacl12-rules/migration-patterns.md b/shacl12-rules/migration-patterns.md index 244c161d..d30759f5 100644 --- a/shacl12-rules/migration-patterns.md +++ b/shacl12-rules/migration-patterns.md @@ -69,7 +69,7 @@ WHERE { ?x a ex:Person . ?x ex:name ?label } --- -## Pattern 2: Type Classification (Triple Rule + `sh:condition`) +## Pattern 2: Type Classification (Triple Rule + `sh:condition`) Classify nodes based on property conditions. @@ -157,7 +157,7 @@ WHERE { --- -## Pattern 3: Computed Values (SPARQL Rule + `BIND`) +## Pattern 3: Computed Values (SPARQL Rule + `BIND`) Compute derived values using arithmetic or string operations. @@ -177,7 +177,7 @@ ex:RectangleRulesShape WHERE { $this ex:width ?width . $this ex:height ?height . - SET (?area := ?width * ?height ) . + BIND(?width * ?height AS ?area) . } """ ; sh:condition ex:RectangleShape ; @@ -194,7 +194,7 @@ WHERE { ?x a ex:Rectangle . ?x ex:width ?width . ?x ex:height ?height . - BIND(?width * ?height AS ?area) + SET (?area := ?width * ?height) } ``` @@ -234,13 +234,19 @@ WHERE { ### Notes -- BIND syntax identical between SPARQL and SRL +- 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) +## Pattern 4: Conditional Inference (`sh:condition` shapes) Apply rules only to nodes conforming to specific shapes. @@ -299,7 +305,7 @@ WHERE { [ sparql:datatype ( [ srl:varName "age" ] ) ] xsd:integer ) ] ] - [ srl:filter [ sparql:greaterThanOrEqual ( + [ srl:filter [ sparql:greater-than-or-equal ( [ srl:varName "age" ] 18 ) ] ] @@ -322,7 +328,7 @@ WHERE { --- -## Pattern 5: Ordered/Chained Rules (`sh:order`) +## Pattern 5: Ordered/Chained Rules (`sh:order`) Rules that must execute in sequence, where later rules depend on earlier results. @@ -383,9 +389,17 @@ WHERE { ### Notes - No `sh:order` needed. The cousin rule references `ex:uncle` in its body, and - the uncle rule produces `ex:uncle` in its head. Stratification automatically - places the uncle rule in a lower (earlier) stratum. -- Both rules run to fixed point within their respective strata. + 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. --- @@ -427,7 +441,7 @@ WHERE { ?x a ex:Rectangle . ?x ex:width ?w . ?x ex:height ?h . - BIND(ex:multiply(?w, ?h) AS ?area) + SET (?area := ex:multiply(?w, ?h)) } ``` @@ -468,9 +482,10 @@ WHERE { ### Notes - Node expression `[ ex:multiply ( [ sh:path ex:width ] [ sh:path ex:height ] ) ]` - decomposes into: path expressions → body patterns, function call → `BIND` expression -- Function IRI (`ex:multiply`) used directly in `BIND` — same calling convention -- Function definition mechanism is in SHACL 1.2 Node Expressions specification, not rules specification + 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 --- @@ -542,7 +557,7 @@ WHERE { --- -## Pattern 8: Multi-Triple Output (SPARQL `CONSTRUCT` with Multiple Patterns) +## Pattern 8: Multi-Triple Output (SPARQL `CONSTRUCT` with Multiple Patterns) Rules that produce multiple triples per match. @@ -583,8 +598,8 @@ WHERE { ?x a ex:Person . ?x ex:firstName ?f . ?x ex:lastName ?l . - BIND(concat(?f, " ", ?l) AS ?full) - BIND(concat(substr(?f, 1, 1), substr(?l, 1, 1)) AS ?init) + SET (?full := CONCAT(?f, " ", ?l)) + SET (?init := CONCAT(SUBSTR(?f, 1, 1), SUBSTR(?l, 1, 1))) } ``` @@ -637,7 +652,10 @@ WHERE { - Multiple triple templates in the head → multiple inferred triples per match - Each head triple template is a separate element in the `srl:head` list -- SPARQL built-in functions (CONCAT, SUBSTR) available via `sparql:` namespace +- 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 --- @@ -653,16 +671,19 @@ For each SHACL-AF rule, follow these steps: 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 +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. + 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 `BIND`; filter shapes become body constraints. + 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`. +7. **Group into rule set** — collect related rules into an `srl:RuleSet`. -8. **Leverage new features** — consider whether negation, recursion, or `TRANSITIVE` +8. **Leverage new features** — consider whether negation, recursion, or `TRANSITIVE` declarations simplify your rules.