From dbe442aa4f78ee3c2ae06b766f9c80f502a2729f Mon Sep 17 00:00:00 2001 From: Holger Knublauch Date: Wed, 17 Jun 2015 18:41:48 +1000 Subject: [PATCH] New version of system vocab and ref document for recursion propagation based on "undefined". Added/updated tests for these changes. --- .../tests/features/core/and-003.ttl | 59 +++++ .../tests/features/core/manifest.ttl | 61 +++++ .../tests/features/core/recursive-001.ttl | 2 +- .../tests/features/core/recursive-002.ttl | 28 +++ .../tests/features/core/recursive-003.ttl | 84 +++++++ shacl-ref/index.html | 230 ++++++++++++++---- shacl/shacl.shacl.ttl | 72 ++++-- 7 files changed, 459 insertions(+), 77 deletions(-) create mode 100644 data-shapes-test-suite/tests/features/core/and-003.ttl create mode 100644 data-shapes-test-suite/tests/features/core/recursive-002.ttl create mode 100644 data-shapes-test-suite/tests/features/core/recursive-003.ttl diff --git a/data-shapes-test-suite/tests/features/core/and-003.ttl b/data-shapes-test-suite/tests/features/core/and-003.ttl new file mode 100644 index 00000000..8427bceb --- /dev/null +++ b/data-shapes-test-suite/tests/features/core/and-003.ttl @@ -0,0 +1,59 @@ +# baseURI: http://www.w3.org/ns/shacl/test/features/core/and-003 + +@prefix ex: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . + +<> + a sh:Graph ; + sh:shapesGraph ; +. + + +# Shape Definitions ----------------------------------------------------------- + +ex:RecursiveShape + a sh:Shape ; + sh:property [ + sh:predicate ex:property2 ; + sh:valueShape ex:RecursiveShape ; + ] ; +. + +ex:AndShape + a sh:Shape ; + sh:constraint [ + a sh:AndConstraint ; + sh:shapes ( + [ + sh:property [ + sh:predicate ex:property1 ; + sh:maxCount 1 ; + ] + ] + ex:RecursiveShape + ) + ] ; +. + +# Instances ------------------------------------------------------------------- + +ex:ValidInstance1 + sh:nodeShape ex:AndShape ; + ex:property1 "One" ; +. + +# Invalid: Infinite loop +ex:InvalidInstance1 + sh:nodeShape ex:AndShape ; + ex:property2 ex:InvalidInstance1 ; +. + +# Invalid: more than one property1 +ex:InvalidInstance2 + sh:nodeShape ex:AndShape ; + ex:property1 "One" ; + ex:property1 "Two" ; +. diff --git a/data-shapes-test-suite/tests/features/core/manifest.ttl b/data-shapes-test-suite/tests/features/core/manifest.ttl index 939d5acc..47ae9133 100644 --- a/data-shapes-test-suite/tests/features/core/manifest.ttl +++ b/data-shapes-test-suite/tests/features/core/manifest.ttl @@ -22,6 +22,8 @@ + + @@ -81,6 +83,24 @@ mf:status sht:proposed ; . + + a sht:Validate ; + mf:name "Test of a sh:AndConstraint" ; + mf:action [ + sht:schema ; + sht:data ; + ] ; + mf:result [ + a sh:FatalError ; + sh:root ex:InvalidInstance1 ; + ] ; + mf:result [ + a sh:Error ; + sh:root ex:InvalidInstance2 ; + ] ; + mf:status sht:proposed ; +. + a sht:Validate ; mf:name "Test of a sh:ShapeClass definition" ; @@ -210,6 +230,13 @@ sht:schema ; sht:data ; ] ; + mf:result [ + a sh:FatalError ; + sh:root ex:Diego ; + sh:subject ex:Diego ; + sh:predicate ex:knows ; + sh:object ex:Alessandro ; + ] ; mf:result [ a sh:Error ; sh:root ex:Enrico ; @@ -220,6 +247,40 @@ mf:status sht:proposed ; . + + a sht:Validate ; + mf:name "Test of a sh:valueShape with recursion" ; + mf:action [ + sht:schema ; + sht:data ; + ] ; + mf:result [ + a sh:FatalError ; + sh:root ex:Instance ; + sh:subject ex:Instance ; + sh:predicate ex:property ; + sh:object ex:Instance ; + ] ; + mf:status sht:proposed ; +. + + + a sht:Validate ; + mf:name "Test of a sh:valueShape with recursion" ; + mf:action [ + sht:schema ; + sht:data ; + ] ; + mf:result [ + a sh:FatalError ; + sh:root ex:Instance1 ; + sh:subject ex:Instance1 ; + sh:predicate ex:someProperty ; + sh:object ex:Instance2 ; + ] ; + mf:status sht:proposed ; +. + a sht:Validate ; mf:name "Test of a sh:scopeShape" ; diff --git a/data-shapes-test-suite/tests/features/core/recursive-001.ttl b/data-shapes-test-suite/tests/features/core/recursive-001.ttl index e2308c87..8ef7ac9c 100644 --- a/data-shapes-test-suite/tests/features/core/recursive-001.ttl +++ b/data-shapes-test-suite/tests/features/core/recursive-001.ttl @@ -65,5 +65,5 @@ ex:Diego ex:knows ex:Alessandro . ex:Enrico ex:knows ex:John . ex:John ex:knows ex:Maurizio . -ex:Diego sh:nodeShape ex:Polentoni . # Correct +ex:Diego sh:nodeShape ex:Polentoni . # Undefined due to infinite loop (Diego -> Alessandro -> Diego) ex:Enrico sh:nodeShape ex:Polentoni . # Incorrect (Enrico -> John -> Maurizio) diff --git a/data-shapes-test-suite/tests/features/core/recursive-002.ttl b/data-shapes-test-suite/tests/features/core/recursive-002.ttl new file mode 100644 index 00000000..359e799c --- /dev/null +++ b/data-shapes-test-suite/tests/features/core/recursive-002.ttl @@ -0,0 +1,28 @@ +# baseURI: http://www.w3.org/ns/shacl/test/features/core/recursive-002 + +@prefix ex: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . + +<> + a sh:Graph ; + sh:shapesGraph ; +. + +# Shapes ---------------------------------------------------------------------- + +ex:MyShape + a sh:Shape ; + sh:property [ + sh:predicate ex:property ; + sh:valueShape ex:MyShape ; + ] ; +. + +# Instances ------------------------------------------------------------------- + +ex:Instance + sh:nodeShape ex:MyShape ; + ex:property ex:Instance . diff --git a/data-shapes-test-suite/tests/features/core/recursive-003.ttl b/data-shapes-test-suite/tests/features/core/recursive-003.ttl new file mode 100644 index 00000000..d66c82ae --- /dev/null +++ b/data-shapes-test-suite/tests/features/core/recursive-003.ttl @@ -0,0 +1,84 @@ +# baseURI: http://www.w3.org/ns/shacl/test/features/core/recursive-003 + +@prefix ex: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . + +<> + a sh:Graph ; + sh:shapesGraph ; + rdfs:comment "Based on an example suggested by Simon in https://lists.w3.org/Archives/Public/public-data-shapes-wg/2015Jun/0083.html" ; +. + +# Shapes ---------------------------------------------------------------------- + +ex:recursionShapeExample + a sh:Shape ; + sh:property [ + sh:predicate ex:someProperty ; + sh:valueShape ex:hasAShape ; + ] . + +ex:hasAShape + a sh:Shape ; + sh:constraint [ + a sh:AndConstraint ; + sh:shapes (ex:ValueTypeAShape ex:notBShape) + ] . + +ex:hasBShape + a sh:Shape ; + sh:constraint [ + a sh:AndConstraint ; + sh:shapes (ex:ValueTypeBShape ex:notAShape) + ] . + +ex:notAShape + a sh:Shape ; + sh:constraint [ + a sh:NotConstraint ; + sh:shape ex:hasAShape ; + ] . + +ex:notBShape + a sh:Shape ; + sh:constraint [ + a sh:NotConstraint ; + sh:shape ex:hasBShape; + ] . + +ex:ValueTypeAShape + a sh:Shape ; + sh:property [ + sh:predicate ex:property ; + sh:valueType ex:ClassA ; + ] . + +ex:ValueTypeBShape + a sh:Shape ; + sh:property [ + sh:predicate ex:property ; + sh:valueType ex:ClassB ; + ] . + + +# Instances ------------------------------------------------------------------- + +ex:InstanceOfA + a ex:ClassA ; +. + +ex:InstanceOfB + a ex:ClassB ; +. + +ex:Instance1 + sh:nodeShape ex:recursionShapeExample ; + ex:someProperty ex:Instance2 ; +. + +ex:Instance2 + ex:property ex:InstanceOfA ; +. diff --git a/shacl-ref/index.html b/shacl-ref/index.html index 114149b5..8facf621 100644 --- a/shacl-ref/index.html +++ b/shacl-ref/index.html @@ -28,20 +28,23 @@

sh:Shape

PropertyTypeCountDescription - sh:scopeClassrdfs:Class[0..]Links a Shape to a class, establishing that all instances of that class are expected to have this shape. + sh:scopesh:Scope[0..]Links a Shape to Scopes that produce the focus nodes that the shape applies to. - sh:scopeShapesh:Shape[0..]Links a Shape to other Shapes that the tested nodes need to fulfill before the constraints of the shape are evaluated. + sh:scopeClassrdfs:Class[0..]Links a Shape to a class, establishing that all instances of that class are expected to have this shape. sh:inversePropertysh:InversePropertyConstraint[0..]Declares that a given incoming reference property is relevant for matching resources. - sh:propertysh:PropertyConstraint[0..]Declares that a given property is relevant for matching resources. + sh:filterShapesh:Shape[0..]Links a Shape to other Shapes that the tested nodes need to fulfill before the constraints of the shape are evaluated. sh:constraintsh:Constraint[0..]Defines arbitrary constraints on the matching resources. Use sh:property for structural property declarations. + + sh:propertysh:PropertyConstraint[0..]Declares that a given property is relevant for matching resources. +
@@ -63,7 +66,7 @@

sh:Constraint

PropertyTypeCountDescription - sh:scopeShapesh:Shape[0..]Links a constraint to Shapes that the tested nodes need to fulfill before the constraint is evaluated. + sh:filterShapesh:Shape[0..]Links a constraint to Shapes that the tested nodes need to fulfill before the constraint is evaluated.
@@ -76,15 +79,6 @@

sh:NativeConstraint

PropertyTypeCountDescription - - sh:sparqlEntailment[0..1]The SPARQL entailment required to execute the SPARQL query. - - - sh:severitysh:ResultClass[0..1]Specifies the default rdf:type to produce for any constraint violation. - - - sh:sparqlxsd:string[0..1]The SPARQL SELECT query to execute. - sh:predicaterdf:Property[0..1]Specifies the default sh:predicate to produce for any constraint violation. @@ -94,6 +88,9 @@

sh:NativeConstraint

sh:messagexsd:string[0..]Specifies the default sh:message(s) to produce for any constraint violation. May have multiple values for different languages. + + sh:severitysh:ResultClass[0..1]Specifies the default rdf:type to produce for any constraint violation. + rdfs:commentxsd:string[0..]A human-readable explanation of this constraint. May have multiple values for different languages. @@ -119,6 +116,9 @@

Macro Classes

+ @@ -176,6 +176,12 @@

sh:Macro

+
+

sh:ScopeTemplate

+

A template that is used to compute a scope.

+

+ Type: sh:ShapeClass

+

sh:Template

A macro that acts as an abstraction of a (reusable) SPARQL query. The query can be parameterized by the supplied arguments.

@@ -185,9 +191,6 @@

sh:Template

PropertyTypeCountDescription - - sh:sparqlxsd:string[0..1]The SPARQL query to execute. - sh:labelTemplatexsd:string[0..]Outlines how human-readable labels of instances of this template class shall be produced. The values must be strings that can contain {?argName} as placeholders for the actual values of the given argument. There may be multiple values, for different languages. @@ -250,25 +253,25 @@

sh:Result

PropertyTypeCountDescription - sh:object[0..1]The object of triples involved in this result. + sh:sourcesh:Constraint[0..]The Constraint that caused this. This property gets filled in automatically by the constraint validation engine. - sh:predicaterdf:Property[0..1]The predicate of triples involved in this result. + sh:rootrdfs:Resource[0..1]The root resource of the result (often: the focus node). - sh:rootrdfs:Resource[0..1]The root resource of the result (often: the focus node). + sh:detailsh:Result[0..]Can link a result with other results that provide more details. This is especially useful to describe violations against nested patterns or shapes. - sh:messagexsd:string[0..]A human-readable message explaining the cause of the result. Multiple values are possible assuming they have different languages. + sh:subject[0..1]The subject of triples involved in this result. - sh:detailsh:Result[0..]Can link a result with other results that provide more details. This is especially useful to describe violations against nested patterns or shapes. + sh:object[0..1]The object of triples involved in this result. - sh:subject[0..1]The subject of triples involved in this result. + sh:messagexsd:string[0..]A human-readable message explaining the cause of the result. Multiple values are possible assuming they have different languages. - sh:sourcesh:Constraint[0..]The Constraint that caused this. This property gets filled in automatically by the constraint validation engine. + sh:predicaterdf:Property[0..1]The predicate of triples involved in this result.
@@ -283,7 +286,7 @@

sh:Warning

Other Classes

sh:Profile

-

A profile is a collection of templates. Profiles can be used to group together templates with similar complexity. Tools can indicate that they support specific profiles only.

+

A profile is a collection of constraint templates and scope templates. Profiles can be used to group together templates with similar complexity. Tools can indicate that they support specific profiles only.

Type: sh:ShapeClass

@@ -291,7 +294,13 @@

sh:Profile

- + + + + + + +
PropertyTypeCountDescription
sh:membersh:Template[0..]sh:extendssh:Profile[0..]Points at another profile that forms a sub-set of this profile.
sh:scopeTemplatesh:ScopeTemplate[0..]A scope template supported by the profile.
sh:constraintTemplatesh:ConstraintTemplate[0..]A constraint template supported by the profile.
@@ -667,6 +676,38 @@

sh:AbstractPatternPropertyConstraint (Abstract Template)

+
+

sh:AbstractQualifiedValueShapePropertyConstraint (Abstract Template)

+

Enforces a constraint that a certain number of values of the property must have a certain shape.

+ + + + + + + + + + + + + + + + +
ArgumentTypeCountDescription
sh:predicaterdf:Property[1..1]The Property being constrained.
sh:qualifiedMaxCountxsd:integer[1..1]The maximum number of values that must have the shape.
sh:qualifiedMinCountxsd:integer[1..1]The minimum number of values that must have the shape.
sh:qualifiedValueShapesh:Shape[1..1]The shape that the values must have.
+
+
SPARQL DEFINITION
+
+		SELECT ?this (?this AS ?subject) ?predicate ?qualifiedValueShape ?qualifiedMinCount ?qualifiedMaxCount ?count ?error
+		WHERE {
+			BIND (sh:valuesWithShapeCount(?this, ?predicate, ?qualifiedValueShape, ?shapesGraph) AS ?count) .
+			BIND (!bound(?count) AS ?error) .
+			FILTER IF(?error, true, ((?count < ?qualifiedMinCount) || (bound(?qualifiedMaxCount) && (?count > ?qualifiedMaxCount)))) .
+		}
+		
+
+

sh:AbstractValueShapePropertyConstraint (Abstract Template)

Enforces a constraint that all values of the property must have a certain shape.

@@ -684,10 +725,12 @@

sh:AbstractValueShapePropertyConstraint (Abstract Template)

SPARQL DEFINITION
-		SELECT ?this (?this AS ?subject) ?predicate ?object ?valueShape
+		SELECT ?this (?this AS ?subject) ?predicate ?object ?valueShape ?error
 		WHERE {
 			?this ?predicate ?object .
-			FILTER (!sh:hasShape(?object, ?valueShape, ?shapesGraph)) .
+			BIND (sh:hasShape(?object, ?valueShape, ?shapesGraph) AS ?hasShape) .
+			BIND (!bound(?hasShape) AS ?error) .
+			FILTER (?error || !?hasShape) .
 		}
 		
@@ -776,6 +819,15 @@

sh:PropertyConstraint (Template)

sh:predicaterdf:Property[1..1]The Property being constrained. + + sh:qualifiedMaxCountxsd:integer[1..1]The maximum number of values that must have the shape. + + + sh:qualifiedMinCountxsd:integer[1..1]The minimum number of values that must have the shape. + + + sh:qualifiedValueShapesh:Shape[1..1]The shape that the values must have. + sh:valueShapesh:Shape[1..1]The shape that the values must have. @@ -1012,14 +1064,11 @@

sh:AndConstraint (Template)

SPARQL DEFINITION
-		SELECT *
+		SELECT ?this ?error
 		WHERE {
-			FILTER EXISTS {
-				GRAPH ?shapesGraph {
-					?shapes rdf:rest*/rdf:first ?shape .
-				}
-				FILTER (!sh:hasShape(?this, ?shape, ?shapesGraph)) .
-			}
+			BIND (sh:walkShapesList(?shapes, false, ?this, ?shapesGraph) AS ?result) .
+			BIND (!bound(?result) AS ?error) .
+			FILTER COALESCE(?result, true) .
 		}
 		
@@ -1057,9 +1106,11 @@

sh:NotConstraint (Template)

SPARQL DEFINITION
-		SELECT *
+		SELECT ?this ?error
 		WHERE {
-			FILTER (sh:hasShape(?this, ?shape, ?shapesGraph)) .
+			BIND (sh:hasShape(?this, ?shape, ?shapesGraph) AS ?hasShape) .
+			BIND (!bound(?hasShape) AS ?error) .
+			FILTER (?error || ?hasShape) .
 		}
 		
@@ -1078,14 +1129,11 @@

sh:OrConstraint (Template)

SPARQL DEFINITION
-		SELECT *
+		SELECT ?this ?error
 		WHERE {
-			FILTER NOT EXISTS {
-				GRAPH ?shapesGraph {
-					?shapes rdf:rest*/rdf:first ?shape .
-				}
-				FILTER sh:hasShape(?this, ?shape, ?shapesGraph) .
-			}
+			BIND (sh:walkShapesList(?shapes, true, ?this, ?shapesGraph) AS ?result) .
+			BIND (!bound(?result) AS ?error) .
+			FILTER COALESCE(!?result, true) .
 		}
 		
@@ -1108,9 +1156,11 @@

sh:XorConstraint (Template)

SPARQL DEFINITION
-		SELECT *
+		SELECT ?this ?error
 		WHERE {
-			FILTER (sh:validShapeCount(?this, ?shapes, ?shapesGraph) != 1) .
+			BIND (sh:validShapeCount(?this, ?shapes, ?shapesGraph) AS ?count)
+			BIND (!bound(?count) AS ?error) .
+			FILTER IF(?error, true, ?count != 1) .
 		}
 		
@@ -1186,7 +1236,7 @@

sh:hasNodeKind (Function)

sh:hasShape (Function)

-

Validates whether a given resource (?arg1) fulfills all error-level constraints defined for a given shape (?arg2). This creates a (possibly recursive) constraint validator.

+

Validates whether a given resource (?arg1) fulfills all error-level constraints defined for a given shape (?arg2). This creates a (possibly recursive) constraint validator. The function returns an error, i.e. no result, if the validation could not be completed, e.g. due to an unsupported recursion or an unsupported extension language. Therefore the function should not be called inside of FILTER statements directly, but rather with BIND.

@@ -1303,7 +1353,7 @@

sh:propertyValue (Function)

sh:validShapeCount (Function)

-

Counts the number of shapes from a given rdf:List (?arg2) defined in a given shapes graph (?arg3) where a given focus node (?arg1) returns no error-level constraint violations.

+

Counts the number of shapes from a given rdf:List (?arg2) defined in a given shapes graph (?arg3) where a given focus node (?arg1) returns no error-level constraint violations. The function produces an error if one of the shapes validated to a fatal error.

ArgumentTypeCountDescription
@@ -1315,7 +1365,7 @@

sh:validShapeCount (Function)

- +
ArgumentTypeCountDescription sh:arg2rdf:List[1..1]The list of shapes to walk through.
sh:arg3rdfs:Resource[1..1]The current shapes graph.sh:arg3rdfs:Resource[1..1]The shapes graph.

@@ -1324,12 +1374,15 @@

sh:validShapeCount (Function)

SPARQL DEFINITION
-		SELECT (COUNT(*) AS ?result)
+		# The SUM will fail with an error if one of the operands is not a number
+		# (this mechanism is used to propagate errors from sh:hasShape calls)
+		SELECT (SUM(?s) AS ?result)
 		WHERE {
 			GRAPH ?arg3 {
 				?arg2 rdf:rest*/rdf:first ?shape .
 			}
-			FILTER (sh:hasShape(?arg1, ?shape, ?arg3)) .
+			BIND (sh:hasShape(?arg1, ?shape, ?arg3) AS ?hasShape) .
+			BIND (IF(bound(?hasShape), IF(?hasShape, 1, 0), 'error') AS ?s) .
 		}
 		
@@ -1361,6 +1414,83 @@

sh:valueCount (Function)

+
+

sh:valuesWithShapeCount (Function)

+

Counts the number of values from a given subject (?arg1) / predicate (?arg2) combination that do not produce any error-level constraint violations for a given shape (?arg3) in a given shapes graph (?arg4). The function produces an error if one of the shapes validated to a fatal error.

+ + + + + + + + + + + + + + + + +
ArgumentTypeCountDescription
sh:arg1rdfs:Resource[1..1]The subject to count the values of.
sh:arg2rdf:Property[1..1]The property to count the values of.
sh:arg3sh:Shape[1..1]The shape to validate.
sh:arg4rdfs:Resource[1..1]The shapes graph.
+

+ Return type: xsd:integer +

+
+
SPARQL DEFINITION
+
+		# The SUM will fail with an error if one of the operands is not a number
+		# (this mechanism is used to propagate errors from sh:hasShape calls)
+		SELECT (SUM(?s) AS ?result)
+		WHERE {
+			?arg1 ?arg2 ?value .
+			BIND (sh:hasShape(?value, ?arg3, ?arg4) AS ?hasShape) .
+			BIND (IF(bound(?hasShape), IF(?hasShape, 1, 0), 'error') AS ?s) .
+		}
+		
+
+
+
+

sh:walkShapesList (Function)

+

Walks a given rdf:List of shapes (?arg1) recursively until it encounters a shape where sh:hasShape returns a provide boolean value (?arg2) for a given focus node (?arg3) using the given shapes graph (?arg4). Returns true if the list has reached such a node, false if it has reached the end of the list. Returns an error if one of the calls to sh:hasShape has produced a fatal error.

+ + + + + + + + + + + + + + + + +
ArgumentTypeCountDescription
sh:arg1rdf:List[1..1]The current rdf:List node in the recursion.
sh:arg2xsd:boolean[1..1]The value to match sh:hasShape against.
sh:arg3rdfs:Resource[1..1]The focus node to validate.
sh:arg4rdfs:Resource[1..1]The shapes graph.
+

+ Return type: xsd:boolean +

+
+
SPARQL DEFINITION
+
+		SELECT ?result
+		WHERE {
+			GRAPH ?arg4 {
+				OPTIONAL {
+					?arg1 rdf:first ?shape .
+					?arg1 rdf:rest ?rest .
+				}
+			}
+			BIND (IF(bound(?shape), sh:hasShape(?arg3, ?shape, ?arg4), !?arg2) AS ?hasShape) .
+			BIND (IF(?hasShape = ?arg2, true, IF(bound(?rest), sh:walkShapesList(?rest, ?arg2, ?arg3, ?arg4), false)) AS ?result) .
+		}
+		
+
+
diff --git a/shacl/shacl.shacl.ttl b/shacl/shacl.shacl.ttl index 3dc3dad3..8bf7d062 100644 --- a/shacl/shacl.shacl.ttl +++ b/shacl/shacl.shacl.ttl @@ -1,7 +1,7 @@ # baseURI: http://www.w3.org/ns/shacl # SHACL - Shapes Constraint Language -# Draft last edited on 2015-06-10 +# Draft last edited on 2015-06-17 # Created by the W3C RDF Data Shapes Working Group # Editor: Holger Knublauch @@ -996,10 +996,11 @@ sh:AbstractQualifiedValueShapePropertyConstraint ] ; sh:message "Violation of qualified value shape constraint {?qualifiedValueShape}: expected [{?qualifiedMinCount}..{?qualifiedMaxCount}], found {?count}" ; sh:sparql """ - SELECT ?this (?this AS ?subject) ?predicate ?qualifiedValueShape ?qualifiedMinCount ?qualifiedMaxCount ?count + SELECT ?this (?this AS ?subject) ?predicate ?qualifiedValueShape ?qualifiedMinCount ?qualifiedMaxCount ?count ?error WHERE { BIND (sh:valuesWithShapeCount(?this, ?predicate, ?qualifiedValueShape, ?shapesGraph) AS ?count) . - FILTER ((?count < ?qualifiedMinCount) || (bound(?qualifiedMaxCount) && (?count > ?qualifiedMaxCount))) . + BIND (!bound(?count) AS ?error) . + FILTER IF(?error, true, ((?count < ?qualifiedMinCount) || (bound(?qualifiedMaxCount) && (?count > ?qualifiedMaxCount)))) . } """ ; . @@ -1020,10 +1021,12 @@ sh:AbstractValueShapePropertyConstraint ] ; sh:message "Value does not fulfill the constraints of shape {?valueShape}" ; sh:sparql """ - SELECT ?this (?this AS ?subject) ?predicate ?object ?valueShape + SELECT ?this (?this AS ?subject) ?predicate ?object ?valueShape ?error WHERE { ?this ?predicate ?object . - FILTER (!sh:hasShape(?object, ?valueShape, ?shapesGraph)) . + BIND (sh:hasShape(?object, ?valueShape, ?shapesGraph) AS ?hasShape) . + BIND (!bound(?hasShape) AS ?error) . + FILTER (?error || !?hasShape) . } """ ; . @@ -1440,9 +1443,11 @@ sh:NotConstraint sh:labelTemplate "Not constraint: {?shapes}" ; sh:message "Violation of NOT constraint" ; sh:sparql """ - SELECT * + SELECT ?this ?error WHERE { - FILTER (sh:hasShape(?this, ?shape, ?shapesGraph)) . + BIND (sh:hasShape(?this, ?shape, ?shapesGraph) AS ?hasShape) . + BIND (!bound(?hasShape) AS ?error) . + FILTER (?error || ?hasShape) . } """ ; . @@ -1461,9 +1466,11 @@ sh:AndConstraint sh:labelTemplate "And constraint: {?shapes}" ; sh:message "Violation of AND constraint" ; sh:sparql """ - SELECT ?this + SELECT ?this ?error WHERE { - FILTER (sh:walkShapesList(?shapes, false, ?this, ?shapesGraph)) + BIND (sh:walkShapesList(?shapes, false, ?this, ?shapesGraph) AS ?result) . + BIND (!bound(?result) AS ?error) . + FILTER COALESCE(?result, true) . } """ ; . @@ -1482,9 +1489,11 @@ sh:OrConstraint sh:labelTemplate "Or constraint: {?shapes}" ; sh:message "Violation of OR constraint" ; sh:sparql """ - SELECT ?this + SELECT ?this ?error WHERE { - FILTER (!sh:walkShapesList(?shapes, true, ?this, ?shapesGraph)) + BIND (sh:walkShapesList(?shapes, true, ?this, ?shapesGraph) AS ?result) . + BIND (!bound(?result) AS ?error) . + FILTER COALESCE(!?result, true) . } """ ; . @@ -1503,9 +1512,11 @@ sh:XorConstraint sh:labelTemplate "Xor constraint: {?shapes}" ; sh:message "Violation of XOR constraint" ; sh:sparql """ - SELECT * + SELECT ?this ?error WHERE { - FILTER (sh:validShapeCount(?this, ?shapes, ?shapesGraph) != 1) . + BIND (sh:validShapeCount(?this, ?shapes, ?shapesGraph) AS ?count) + BIND (!bound(?count) AS ?error) . + FILTER IF(?error, true, ?count != 1) . } """ ; . @@ -1567,7 +1578,7 @@ sh:hasShape a sh:Function ; rdfs:subClassOf sh:Functions ; rdfs:label "has shape" ; - rdfs:comment "Validates whether a given resource (?arg1) fulfills all error-level constraints defined for a given shape (?arg2). This creates a (possibly recursive) constraint validator." ; + rdfs:comment "Validates whether a given resource (?arg1) fulfills all error-level constraints defined for a given shape (?arg2). This creates a (possibly recursive) constraint validator. The function returns an error, i.e. no result, if the validation could not be completed, e.g. due to an unsupported recursion or an unsupported extension language. Therefore the function should not be called inside of FILTER statements directly, but rather with BIND." ; sh:argument [ sh:predicate sh:arg1 ; sh:valueType rdfs:Resource ; @@ -1675,7 +1686,7 @@ sh:validShapeCount a sh:Function ; rdfs:subClassOf sh:Functions ; rdfs:label "valid shape count" ; - rdfs:comment "Counts the number of shapes from a given rdf:List (?arg2) defined in a given shapes graph (?arg3) where a given focus node (?arg1) returns no error-level constraint violations." ; + rdfs:comment "Counts the number of shapes from a given rdf:List (?arg2) defined in a given shapes graph (?arg3) where a given focus node (?arg1) returns no error-level constraint violations. The function produces an error if one of the shapes validated to a fatal error." ; sh:argument [ sh:predicate sh:arg1 ; sh:valueType rdfs:Resource ; @@ -1693,12 +1704,15 @@ sh:validShapeCount ] ; sh:returnType xsd:integer ; sh:sparql """ - SELECT (COUNT(*) AS ?result) + # The SUM will fail with an error if one of the operands is not a number + # (this mechanism is used to propagate errors from sh:hasShape calls) + SELECT (SUM(?s) AS ?result) WHERE { GRAPH ?arg3 { ?arg2 rdf:rest*/rdf:first ?shape . } - FILTER (sh:hasShape(?arg1, ?shape, ?arg3)) . + BIND (sh:hasShape(?arg1, ?shape, ?arg3) AS ?hasShape) . + BIND (IF(bound(?hasShape), IF(?hasShape, 1, 0), 'error') AS ?s) . } """ ; . @@ -1731,7 +1745,7 @@ sh:valuesWithShapeCount a sh:Function ; rdfs:subClassOf sh:Functions ; rdfs:label "values with shape count" ; - rdfs:comment "Counts the number of values from a given subject (?arg1) / predicate (?arg2) combination that do not produce any error-level constraint violations for a given shape (?arg3) in a given shapes graph (?arg4)." ; + rdfs:comment "Counts the number of values from a given subject (?arg1) / predicate (?arg2) combination that do not produce any error-level constraint violations for a given shape (?arg3) in a given shapes graph (?arg4). The function produces an error if one of the shapes validated to a fatal error." ; sh:argument [ sh:predicate sh:arg1 ; sh:valueType rdfs:Resource ; @@ -1754,10 +1768,13 @@ sh:valuesWithShapeCount ] ; sh:returnType xsd:integer ; sh:sparql """ - SELECT (COUNT(*) AS ?result) + # The SUM will fail with an error if one of the operands is not a number + # (this mechanism is used to propagate errors from sh:hasShape calls) + SELECT (SUM(?s) AS ?result) WHERE { ?arg1 ?arg2 ?value . - FILTER (sh:hasShape(?value, ?arg3, ?arg4)) . + BIND (sh:hasShape(?value, ?arg3, ?arg4) AS ?hasShape) . + BIND (IF(bound(?hasShape), IF(?hasShape, 1, 0), 'error') AS ?s) . } """ ; . @@ -1766,7 +1783,7 @@ sh:walkShapesList a sh:Function ; rdfs:subClassOf sh:Functions ; rdfs:label "walk shapes list" ; - rdfs:comment "Walks a given rdf:List of shapes (?arg1) recursively until it encounters a shape where sh:hasShape returns a provide boolean value (?arg2) for a given focus node (?arg3) using the given shapes graph (?arg4). Returns true if the list has reached such a node, false if it has reached the end of the list." ; + rdfs:comment "Walks a given rdf:List of shapes (?arg1) recursively until it encounters a shape where sh:hasShape returns a provide boolean value (?arg2) for a given focus node (?arg3) using the given shapes graph (?arg4). Returns true if the list has reached such a node, false if it has reached the end of the list. Returns an error if one of the calls to sh:hasShape has produced a fatal error." ; sh:argument [ sh:predicate sh:arg1 ; sh:valueType rdf:List ; @@ -1789,13 +1806,16 @@ sh:walkShapesList ] ; sh:returnType xsd:boolean ; sh:sparql """ - ASK { + SELECT ?result + WHERE { GRAPH ?arg4 { - ?arg1 rdf:first ?shape . - ?arg1 rdf:rest ?rest . + OPTIONAL { + ?arg1 rdf:first ?shape . + ?arg1 rdf:rest ?rest . + } } - BIND (sh:hasShape(?arg3, ?shape, ?arg4) AS ?hasShape) . - FILTER IF(?hasShape = ?arg2, true, sh:walkShapesList(?rest, ?arg2, ?arg3, ?arg4)) . + BIND (IF(bound(?shape), sh:hasShape(?arg3, ?shape, ?arg4), !?arg2) AS ?hasShape) . + BIND (IF(?hasShape = ?arg2, true, IF(bound(?rest), sh:walkShapesList(?rest, ?arg2, ?arg3, ?arg4), false)) AS ?result) . } """ ; .