Re: ISSUE-139: implementing (core) constraint components universally

So in the current specification of SHACL:
- Having up to three pieces of code per constraint component is less than
ideal.
- Having considerable code to go from the focus node to the value nodes is
less than ideal.
- It is probably impossible to have a single simple piece of SPARQL code
per constraint component.
So why not then search for a change to SHACL that can achieve single simple
SPARQL implementations of all constraint components?

peter




On 06/03/2016 05:14 PM, Holger Knublauch wrote:
> Hi Peter,
>
> thanks for the discussion - this is an important topic and worth drilling into.
>
> On 4/06/2016 0:12, Peter F. Patel-Schneider wrote:
>> My original message in this thread is mostly concerned with how to implement
>> constraint components universally.  There is also a short preamble on how
>> one can best describe how constraint components.  I'm going to defend both
>> of these points separately, but I'm going to start with the implementation
>> point as that is the bulk of my original message.
>>
>>
>> Right now constraint components have up to three different implementations -
>> one when they occur in a property constraint, one when they occur in an
>> inverse property constraint, and one when they occur in a node constraint.
>> This means that there are up to three different pieces of code for each
>> constraint component, each (hopefully) implementing the same functionality.
>> I view this as a poor setup - three different pieces of code that have to be
>> written and thus three places where the bugs can be introduced.
>
> I fully agree.
>
>>
>> Having a single implementation of each constraint component would actually
>> reduce development costs.  Ideally, this single implementation would be as
>> simple as the ask validators that implement many constraint components.
>> Consider, for example, sh:minCount whose implementation should be very
>> little more than "HAVING ( COUNT (DISTINCT ?value) < ?minCount )".
>
> Yes, if this were possible then this would be ideal.
>
>>    However,
>> I can't figure out how to do this nicely because of limitations in SPARQL,
>> hence the solution with boilerplate.
>
> Exactly that's the same conclusion that I also have made. Furthermore I
> remember long discussions with Arthur on the phone in November. He had also
> questioned why we cannot combine all these cases. But he also did not come up
> with a better solution. If all three of us don't come up with a solution then
> maybe there is none.
>
>>    However, even the boilerplate solution
>> has only one implementation of each constraint component, and here one is
>> definitely better than three and also better than two.
>
> The boilerplate solution that you have described is already covered in the
> spec. Look at 6.5.2 on ASK validators, which enumerate these boilerplate
> snippets as "templates":
>
> http://w3c.github.io/data-shapes/shacl/#SPARQLAskValidator
>
> Users already have the choice to just specify a single ASK query to cover all
> three cases. In my current implementation this technique is used for a large
> number of constraint components. To reproduce, open the attached copy of
> dash.ttl and run this query:
>
> SELECT *
> WHERE {
>     ?cc a sh:ConstraintComponent .
>     OPTIONAL {
>         ?cc sh:nodeValidator ?nodeValidator
>     }
>     OPTIONAL {
>         ?cc sh:propertyValidator ?propValidator
>     }
>     OPTIONAL {
>         ?cc sh:inversePropertyValidator ?invValidator
>     }
> }
>
> Results:
>
> 14 constraint components currently use ASK queries:
>
> sh:ClassConstraintComponent    dash:hasClass    dash:hasClass dash:hasClass
> sh:ClassInConstraintComponent    dash:hasClassIn dash:hasClassIn
> dash:hasClassIn
> sh:DatatypeConstraintComponent    dash:hasDatatype dash:hasDatatype
> sh:DatatypeInConstraintComponent    dash:hasDatatypeIn dash:hasDatatypeIn
> sh:InConstraintComponent    dash:isIn    dash:isIn    dash:isIn
> sh:MaxExclusiveConstraintComponent    dash:hasMaxExclusive dash:hasMaxExclusive
> sh:MaxInclusiveConstraintComponent    dash:hasMaxInclusive dash:hasMaxInclusive
> sh:MaxLengthConstraintComponent    dash:hasMaxLength dash:hasMaxLength
> dash:hasMaxLength
> sh:MinExclusiveConstraintComponent    dash:hasMinExclusive dash:hasMinExclusive
> sh:MinInclusiveConstraintComponent    dash:hasMinInclusive dash:hasMinInclusive
> sh:MinLengthConstraintComponent    dash:hasMinLength dash:hasMinLength
> dash:hasMinLength
> sh:NodeKindConstraintComponent    dash:hasNodeKind dash:hasNodeKind
> dash:hasNodeKind
> sh:PatternConstraintComponent    dash:hasPattern dash:hasPattern
> dash:hasPattern
> sh:StemConstraintComponent    dash:hasStem    dash:hasStem dash:hasStem
>
> The remaining 15 ones are heterogeneous and do not easily fit into that scheme:
>
> - sh:DisjointConstraintComponent
> - sh:LessThanConstraintComponent
> - sh:LessThanOrEqualsConstraintComponent
> These look like they could be turned into ASK queries, so please consider them
> in the category above.
>
> - sh:AndConstraintComponent
> - sh:NotConstraintComponent
> - sh:OrConstraintComponent
> - sh:ShapeConstraintComponent
> These have SELECT queries because the ASK schema (currently) does not support
> handling of the ?failure variable. I cannot tell yet how common the ?failure
> handling will be and whether we need to come up with a different syntax for
> them. The problem is that ASK can only return true or false, but not return a
> thirs value, and there is no "exception" reporting in SPARQL.
>
> - sh:ClosedConstraintComponent
> This uses a SELECT query because the result variable ?predicate is different
> each time and needs to be computed as part of the WHERE clause.
>
> - sh:EqualsConstraintComponent
> This is a SELECT query because it requires two branches in a UNION. The
> boilerplate would not work IMHO.
>
> - sh:HasValueConstraintComponent
> This does not fit into the ASK schema. While theoretically it would be
> possible to use
>
>     ASK { FILTER sameTerm(?value, $hasValue) }
>
> for node constraints, the performance of this would be prohibitively slow for
> the predicate-based constraints. The query in those cases looks very different:
>
>     SELECT $this ($this AS ?object) $predicate
>     WHERE {
>         FILTER NOT EXISTS { $hasValue $predicate $this }
>     }
>
> Furthermore, this is an existential FILTER that does not follow the "usual"
> pattern.
>
> - sh:MaxCountConstraintComponent
> - sh:MinCountConstraintComponent
> - sh:QualifiedMaxCountConstraintComponent
> - sh:QualifiedMinCountConstraintComponent
> Use yet another pattern, where there is either a HAVING clause with an
> aggregation, or a nested query.
>
> - sh:UniqueLangConstraintComponent
> This is a SELECT query because the ?lang is also being returned so that it can
> be used in the sh:message. Also, there should only be one validation result
> per ?lang, and thus it needs to be turned into a SELECT DISTINCT.
>
> *So among the 12 constraint components that are currently not covered by ASKs,
> there are already 6 different design patterns.*
>
> And we have not even started to look into extensions. Whatever further
> generalization we would come up with will almost certainly limit the
> expressivity of SHACL to only a subset of SPARQL, and this would be a show
> stopper.
>
> And then we have not even started to look into other extension languages like
> JavaScript... The current infrastructure is set up so that each case can have
> multiple validators, in multiple languages. A JavaScript-based implementation
> will likely not use SPARQL but instead have completely different code paths to
> walk the objects being validated.
>
> Having thought about all these topics for many months now, my conclusion is
> that we will continue to need the flexibility of multiple validators for the
> different cases. In a large number of cases a single ASK query will be
> sufficient for all three cases. And then in a further large number of cases,
> people will only need to develop one query for node constraints, and one for
> path-based constraints. I am convinced that this will be acceptable (assuming
> you agree we should support paths - your own proposal had them).
>
>
>>
>>
>> Describing all constraint components in a similar fashion is also desirable
>> to describing them differently.  Right now some constraint components, e.g.,
>> sh:class, are described using the notion of value nodes but others, e.g.,
>> sh:minCount, are described using focus nodes and predicates even when the
>> effect is the same as value nodes.
>
> You keep bringing up the same first paragraphs in the spec :) These are
> usually just editorial left-overs from the olden days when the spec was just
> in one direction. These are easy to fix once spotted:
>
>
https://github.com/w3c/data-shapes/commit/66525d7f3f784822806f5d74e54818206d01d6ef
>
>
> Can you find any more such examples? They are just editorial mistakes.
>
>>    Regularizing the way that constraint
>> components are described would reduce the number of ways that errors can
>> creep into the document and also reduce the cognitive load on readers of the
>> document.  Describing constraint components in terms of value nodes also
>> better shows the commonalities amongst them.
>>
>> It is currently possible to have a constraint component that works
>> completely differently when it is in a node constraint from when it is in a
>> property constraint and from when it is in an inverse property constraint.
>> Using the notion of value nodes produces a force against this divergence.
>
> Agreed.
>
>>
>>
>> These issues arise from having all constraint components sit inside the
>> three different kinds of constratins and having each constraint component
>> being responsible for its own determination of value nodes.  There are
>> different approaches to SHACL that would eliminate these issues.  ShEx has a
>> single property-crossing construct and all other constructs in triple
>> expressions are not concerned with properties.  OWL has several
>> property-crossing constructs but most constructs in OWL work on individual
>> value nodes.  My refactored SHACL syntax has a single property-crossing
>> construct and all constructs work on sets of value nodes.
>
> I have explained above why there are differences in the queries, and why these
> differences are important (e.g. in the case of sh:hasValue). While I share
> your desire to further generalize and clean up the language, there are limits
> where this becomes impractical or would otherwise limit the expressive power
> of what customers will want to do. And looking back at many years of working
> with customers and SPIN, the only thing we can predict is that we cannot
> predict the variety of use cases. We need to design the language to cater for
> this flexibility, not make premature assumptions on the limited set of
> examples that happen to be in the Core Vocabulary.
>
> Thanks,
> Holger
>
>
>>
>> peter
>>
>>
>> On 06/02/2016 10:13 PM, Holger Knublauch wrote:
>>> Could you help me understand why we should do this? All I am seeing is that
>>> this would add complexity to the language, add development costs for these
>>> additional cases, increase our burden to specify and write test cases for all
>>> these scenarios, for the "benefit" that people can apply entirely useless
>>> constructs such as minCount with node constraints or datatypes for subjects
>>> which can never be literals.
>>>
>>> Furthermore, deleting the concept of sh:context makes it impossible for tools
>>> to determine under which conditions a constraint component should be offered.
>>> The forms that I have implemented would display every constraint property on
>>> every case - node constraints, property constraints, inverse property
>>> constraints. This is not user friendly!
>>>
>>> Finally, every extension developer is forced to specify SPARQL queries for all
>>> cases, even if they make no sense (like most of the cases below). Some of the
>>> queries that you have written up are completely different from their other
>>> variations. How can you be sure that the same generalization is sensible for
>>> every possible future extension?
>>>
>>> As a random example consider one of the original Use cases: specifying a
>>> primary key. These are only ever meant to be used for properties, neither
>>> inverses nor node constraints nor paths.
>>>
>>> https://www.w3.org/TR/shacl-ucr/#uc25-primary-keys-with-uri-patterns
>>>
>>> I must be missing something, but this is a massive step backwards and a
>>> serious risk to the success of SHACL. There is nothing broken right now with
>>> the context mechanism. Why change it?
>>>
>>> Thanks,
>>> Holger
>>>
>>>
>>> On 3/06/2016 7:19, Peter F. Patel-Schneider wrote:
>>>> To think about how a constraint component works universally, it is
>>>> sufficient to think about value nodes, which are already defined at the
>>>> beginning of Section 4.
>>>>
>>>> So, sh:hasValue is then just that a value node is the given node and
>>>> sh:equals is just that the set of value nodes is the same as the set of
>>>> values for the focus node for the other property and sh:closed is just that
>>>> every value node has no values for disallowed properties and sh:minCount is
>>>> just that there are at least n value nodes.
>>>>
>>>>
>>>> Looking at https://github.com/TopQuadrant/shacl the changes to permit core
>>>> constraint components to be used universally appear to be as follows:
>>>>
>>>> 1/ Ensure that sh:context has all three relevant values for each constraint
>>>> component.  (Of course then sh:context becomes irrelevant and can be
>>>> removed.)
>>>>
>>>> 2/ For the constraint component for:
>>>>
>>>> sh:closed add
>>>>     sh:propertyValidator [
>>>>         rdf:type sh:SPARQLSelectValidator ;
>>>>         sh:message "Predicate {?unallowed} is not allowed on {?subject}
>>>> (closed
>>>> shape)" ;
>>>>         sh:sparql """
>>>>          SELECT ?this (?val AS ?subject) ?unallowed ?object
>>>>          WHERE {
>>>>              {
>>>>                  FILTER ($closed) .
>>>>              }
>>>>              $this $predicate ?val .
>>>>              ?val ?unallowed ?object .
>>>>              FILTER (NOT EXISTS {
>>>>                  GRAPH $shapesGraph {
>>>>                      $currentShape sh:property/sh:predicate ?unallowed .
>>>>                  }
>>>>              } && (!bound($ignoredProperties) || NOT EXISTS {
>>>>                  GRAPH $shapesGraph {
>>>>                      $ignoredProperties rdf:rest*/rdf:first ?unallowed .
>>>>                  }
>>>>              }))
>>>>          }
>>>> """ ;
>>>> Similar for inverse property constraint.
>>>> sh:closed should also be implementable using the simple form (like
>>>> sh:datatype and sh:minExclusive are).
>>>>
>>>> sh:datatype    add dash:hasDatatype as a value for
>>>> sh:inversePropertyValidator
>>>> sh:datatypeIn    add dash:hasDatatypeIn as a value for
>>>> sh:inversePropertyValidator
>>>>
>>>> sh:hasValue    add
>>>>     sh:nodeValidator [
>>>>         rdf:type sh:SPARQLSelectValidator ;
>>>>         sh:message "Node is not value {$hasValue}" ;
>>>>         sh:sparql """
>>>>          SELECT $this
>>>>          WHERE {
>>>>              FILTER { NOT sameTerm($this,$hasValue) }
>>>>          }
>>>>          """ ;
>>>>       ] ;
>>>>
>>>> sh:disjoint add
>>>>     sh:inversePropertyValidator [
>>>>         rdf:type sh:SPARQLSelectValidator ;
>>>>         sh:message "Inverse of property must not share any values with
>>>> {$disjoint}" ;
>>>>         sh:sparql """
>>>>          SELECT $this ($this AS ?object) $predicate ?subject
>>>>          WHERE {
>>>>              ?subject $predicate $this .
>>>>              ?subject $disjoint $this  .
>>>>          }
>>>>          """ ;
>>>>       ] ;
>>>>     sh:nodeValidator [
>>>>         rdf:type sh:SPARQLSelectValidator ;
>>>>         sh:message "Node must not be a value of {$disjoint}" ;
>>>>         sh:sparql """
>>>>          SELECT $this
>>>>          WHERE {
>>>>              $this $disjoint ?this .
>>>>          }
>>>>          """ ;
>>>>       ] ;
>>>>
>>>> sh:equals add
>>>>     sh:inversePropertyValidator [
>>>>         rdf:type sh:SPARQLSelectValidator ;
>>>>         sh:message "Inverse of property must have same values as {$equals}" ;
>>>>         sh:sparql """
>>>>          SELECT $this ($this AS ?object) $predicate ?subject
>>>>          WHERE {
>>>>              {
>>>>                  ?subject $predicate $this .
>>>>                  FILTER NOT EXISTS {
>>>>                      ?subject $equals $this  .
>>>>                  }
>>>>              }
>>>>              UNION
>>>>              {
>>>>                  ?subject $equals $this .
>>>>                  FILTER NOT EXISTS {
>>>>                      ?subject $predicate $this .
>>>>                  }
>>>>              }
>>>>          }
>>>>          """ ;
>>>>       ] ;
>>>>     sh:nodeValidator [
>>>>         rdf:type sh:SPARQLSelectValidator ;
>>>>         sh:message "Node must be a value of {$equals}" ;
>>>>         sh:sparql """
>>>>          SELECT $this
>>>>          WHERE {
>>>>              FILTER NOT EXISTS { $this $disjoint $this }
>>>>          }
>>>>          """ ;
>>>>       ] ;
>>>>
>>>> sh:lessThan add
>>>>     sh:InversePropertyValidator [
>>>>         rdf:type sh:SPARQLSelectValidator ;
>>>>         sh:message "Inverse property value is not < value of {$lessThan}" ;
>>>>         sh:sparql """
>>>>          SELECT $this ($this AS ?object) $predicate ?subject
>>>>          WHERE {
>>>>              ?subject $predicate $this  .
>>>>                $this $lessThan ?object2  .
>>>>              FILTER (!(?subject < ?object2)) .
>>>>          }
>>>>          """ ;
>>>>       ] ;
>>>>     sh:nodeValidator [
>>>>         rdf:type sh:SPARQLSelectValidator ;
>>>>         sh:message "Node is not < value of {$lessThan}" ;
>>>>         sh:sparql """
>>>>          SELECT $this
>>>>          WHERE {
>>>>              $this $lessThan ?object2 .
>>>>              FILTER (!(?this < ?object2)) .
>>>>          }
>>>>          """ ;
>>>>       ] ;
>>>>
>>>> sh:lessThanOrEquals similar
>>>>
>>>> sh:minCount add
>>>>     sh:nodeValidator [
>>>>         rdf:type sh:SPARQLSelectValidator ;
>>>>         sh:message "Node is precisely one value, not {$minCount}" ;
>>>>         sh:sparql """
>>>>          SELECT $this
>>>>          WHERE {
>>>>              FILTER ( 1 >= $minCount) .
>>>>          }
>>>>          """ ;
>>>>       ] ;
>>>>
>>>> sh:maxCount similar
>>>>
>>>> sh:maxExclusive    add dash:hasMaxExclusive as a value for
>>>> sh:inversePropertyValidator
>>>>
>>>> sh:maxInclusive    add dash:hasMaxInclusive as a value for
>>>> sh:inversePropertyValidator
>>>>
>>>> sh:minExclusive    add dash:hasMinExclusive as a value for
>>>> sh:inversePropertyValidator
>>>>
>>>> sh:minInclusive    add dash:hasMinInclusive as a value for
>>>> sh:inversePropertyValidator
>>>>
>>>> sh:uniqueLang add
>>>>     sh:inversePropertyValidator [
>>>>         rdf:type sh:SPARQLSelectValidator ;
>>>>         sh:message "Language {?lang} used more than once" ;
>>>>         sh:sparql """
>>>>          SELECT DISTINCT $this ($this AS ?object) $predicate ?lang
>>>>          WHERE {
>>>>              {
>>>>                  FILTER ($uniqueLang) .
>>>>              }
>>>>              ?value $predicate $this .
>>>>              BIND (lang(?value) AS ?lang) .
>>>>              FILTER (bound(?lang) && ?lang != \"\") .
>>>>              FILTER EXISTS {
>>>>                  $this $predicate ?otherValue .
>>>>                  FILTER (?otherValue != ?value && ?lang =
>>>> lang(?otherValue)) .
>>>>              }
>>>>          }
>>>>          """ ;
>>>>       ] ;
>>>>     sh:nodeValidator [
>>>>         rdf:type sh:SPARQLSelectValidator ;
>>>>         sh:message "A language used more than once on node" ;
>>>>         sh:sparql """
>>>>          SELECT $this
>>>>          WHERE { FILTER ( 1 = 0 )
>>>>          }
>>>>          """ ;
>>>>       ] ;
>>>>
>>>> sh:qualifiedMinCount add
>>>>     sh:nodeValidator [
>>>>         rdf:type sh:SPARQLSelectValidator ;
>>>>         sh:sparql """
>>>>          SELECT $this ($this AS ?subject) $predicate ?count ?failure
>>>>          WHERE {
>>>>              BIND (sh:hasShape(?subject, $valueShape, $shapesGraph) AS
>>>> ?hasShape) .
>>>>              BIND (!bound(?hasShape) AS ?failure) .
>>>>              FILTER IF(?failure, true, ?count > IF(?hasShape,1,0))
>>>>          }
>>>> """ ;
>>>>       ] ;
>>>>
>>>> sh:qualifiedMaxCount similar
>>>>
>>>>
>>>> Note that none of these are difficult to do, particularly when looking at
>>>> the another validator for the same component.  This should be true for any
>>>> constraint component that can be described as working on the value nodes.  I
>>>> think that all constraint components should be describable this way.
>>>>
>>>>
>>>> peter
>>>>
>>>
>
>
> dash.ttl
>
>
> # baseURI: http://datashapes.org/dash
> # imports: http://topbraid.org/tosh
> # imports: http://www.w3.org/ns/shacl#
> # prefix: dash
>
> @prefix dash: <http://datashapes.org/dash#> .
> @prefix owl: <http://www.w3.org/2002/07/owl#> .
> @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
> @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
> @prefix sh: <http://www.w3.org/ns/shacl#> .
> @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
>
> <http://datashapes.org/dash>
>   rdf:type owl:Ontology ;
>   rdfs:comment """DASH implements a standard SPARQL implementation of SHACL
and is utilized by TopBraid and its API.
>
> DASH is also a SHACL library for frequently needed features and design
patterns. All features in this library are 100% standards compliant and will
work on any engine that fully supports SHACL.""" ;
>   rdfs:label "DASH Data Shapes Library" ;
>   owl:imports <http://topbraid.org/tosh> ;
>   owl:imports sh: ;
> .
> dash:FailureResult
>   rdf:type rdfs:Class ;
>   rdf:type sh:Shape ;
>   rdfs:comment "A result representing a validation failure such as an
unsupported recursion." ;
>   rdfs:label "Failure result" ;
>   rdfs:subClassOf sh:AbstractResult ;
> .
> dash:FailureTestCaseResult
>   rdf:type rdfs:Class ;
>   rdf:type sh:Shape ;
>   rdfs:comment "Represents a failure of a test case." ;
>   rdfs:label "Failure test case result" ;
>   rdfs:subClassOf dash:TestCaseResult ;
> .
> dash:FunctionTestCase
>   rdf:type rdfs:Class ;
>   rdf:type sh:Shape ;
>   rdfs:comment "A test case that verifies that a given SPARQL expression
produces a given, expected result." ;
>   rdfs:label "Function test case" ;
>   rdfs:subClassOf dash:TestCase ;
>   sh:property [
>       sh:predicate dash:expectedResult ;
>       sh:description "The expected result of a function call." ;
>       sh:maxCount 1 ;
>       sh:name "expected result" ;
>     ] ;
>   sh:property [
>       sh:predicate dash:expression ;
>       sh:description "A valid SPARQL expression calling the function to test." ;
>       sh:maxCount 1 ;
>       sh:minCount 1 ;
>       sh:name "expression" ;
>     ] ;
> .
> dash:GraphUpdate
>   rdf:type rdfs:Class ;
>   rdf:type sh:Shape ;
>   rdfs:label "Graph update" ;
>   rdfs:subClassOf dash:Suggestion ;
> .
> dash:GraphValidationTestCase
>   rdf:type rdfs:Class ;
>   rdf:type sh:Shape ;
>   rdfs:comment "A test case that performs SHACL constraint validation on the
whole graph and compares the results with the expected validation results
stored with the test case." ;
>   rdfs:label "Graph validation test case" ;
>   rdfs:subClassOf dash:ValidationTestCase ;
> .
> dash:InferencingTestCase
>   rdf:type rdfs:Class ;
>   rdf:type sh:Shape ;
>   rdfs:comment "A test case to verify whether an inferencing engine is
producing identical results to those stored as expected results." ;
>   rdfs:label "Inferencing test case" ;
>   rdfs:subClassOf dash:TestCase ;
>   sh:property [
>       sh:predicate dash:expectedResult ;
>       sh:description "The expected inferred triples, represented by
instances of rdfs:Statement." ;
>       sh:name "expected result" ;
>     ] ;
> .
> dash:MemberShapeConstraintComponent
>   rdf:type sh:ConstraintComponent ;
>   rdfs:comment "Can be used to specify constraints on the members of a given
list, assuming that the given sh:property has rdf:Lists as values. A violation
is reported for each member of the list that does not comply with the
constraints specified by the given shape." ;
>   rdfs:label "Member shape constraint component" ;
>   sh:context sh:PropertyConstraint ;
>   sh:parameter [
>       sh:predicate dash:memberShape ;
>       sh:class sh:Shape ;
>       sh:description "The shape that the list members must have." ;
>       sh:name "member shape" ;
>     ] ;
>   sh:propertyValidator [
>       rdf:type sh:SPARQLSelectValidator ;
>       sh:message "List member {?member} does not have the shape
{$memberShape}" ;
>       sh:select """
>   SELECT $this ($this AS ?subject) $predicate ?object ?failure ?member
>   WHERE {
>    $this $predicate ?object .
>    ?object rdf:rest*/rdf:first ?member .
>    BIND (sh:hasShape(?member, $memberShape, $shapesGraph) AS ?hasShape) .
>    BIND (!bound(?hasShape) AS ?failure) .
>    FILTER (?failure || !?hasShape) .
>   }
> """ ;
>     ] ;
> .
> dash:None
>   rdf:type sh:Shape ;
>   rdfs:comment "A Shape that is violated for every possible focus node. This
can be used in sh:filterShape statements to essentially deactivate other
shapes or constraints." ;
>   rdfs:label "None" ;
>   sh:constraint [
>       sh:in () ;
>     ] ;
> .
> dash:PrimaryKeyConstraintComponent
>   rdf:type sh:ConstraintComponent ;
>   rdfs:comment "Enforces a constraint that the given property (sh:predicate)
serves as primary key for all resources in the scope of the shape. If a
property has been declared to be the primary key then each resource must have
exactly one value for that property. Furthermore, the URIs of those resources
must start with a given string (dash:uriStart), followed by the URL-encoded
primary key value. For example if dash:uriStart is
\"http://example.org/country-\" and the primary key for an instance is \"de\"
then the URI must be \"http://example.org/country-de\". Finally, as a result
of the URI policy, there can not be any other resource with the same value
under the same primary key policy." ;
>   rdfs:label "Primary key constraint component" ;
>   sh:context sh:PropertyConstraint ;
>   sh:labelTemplate "The property {?predicate} is the primary key and URIs
start with {?uriStart}" ;
>   sh:parameter [
>       sh:predicate dash:uriStart ;
>       sh:datatype xsd:string ;
>       sh:description "The start of the URIs of well-formed resources." ;
>       sh:name "URI start" ;
>     ] ;
>   sh:propertyValidator [
>       rdf:type sh:SPARQLSelectValidator ;
>       sh:select """SELECT $this ($this AS ?subject) $predicate (?value AS
?object) ?message
> WHERE {
>     {
>         FILTER NOT EXISTS {
>             ?this $predicate ?any .
>         } .
>         BIND (\"Missing value for primary key property\" AS ?message) .
>     }
>     UNION
>     {
>         FILTER (dash:valueCount(?this, $predicate) > 1) .
>         BIND (\"Multiple values of primary key property\" AS ?message) .
>     }
>     UNION
>     {
>         FILTER (dash:valueCount(?this, $predicate) = 1) .
>         ?this $predicate ?value .
>         BIND (CONCAT($uriStart, ENCODE_FOR_URI(str(?value))) AS ?uri) .
>         FILTER (str(?this) != ?uri) .
>         BIND (CONCAT(\"Primary key value \", str(?value), \" does not align
with the expected URI \", ?uri) AS ?message) .
>     } .
> }""" ;
>     ] ;
> .
> dash:QueryTestCase
>   rdf:type rdfs:Class ;
>   rdf:type sh:Shape ;
>   rdfs:comment "A test case running a given SPARQL SELECT query and
comparing its results with those stored as JSON Result Set in the expected
result property." ;
>   rdfs:label "Query test case" ;
>   rdfs:subClassOf dash:TestCase ;
>   sh:property [
>       sh:predicate dash:expectedResult ;
>       sh:datatype xsd:string ;
>       sh:description "The expected result set, as a JSON string." ;
>       sh:maxCount 1 ;
>       sh:minCount 1 ;
>       sh:name "expected result" ;
>     ] ;
>   sh:property [
>       sh:predicate sh:select ;
>       sh:datatype xsd:string ;
>       sh:description "The SPARQL SELECT query to execute." ;
>       sh:maxCount 1 ;
>       sh:minCount 1 ;
>       sh:name "SPARQL query" ;
>     ] ;
> .
> dash:RootClassConstraintComponent
>   rdf:type sh:ConstraintComponent ;
>   rdfs:comment "A constraint component defining the parameter
dash:rootClass, which restricts the values to be either the root class itself
or one of its subclasses. This is typically used in conjunction with
properties that have rdfs:Class as their type." ;
>   rdfs:label "Root class constraint component" ;
>   sh:context sh:InversePropertyConstraint ;
>   sh:context sh:NodeConstraint ;
>   sh:context sh:PropertyConstraint ;
>   sh:inversePropertyValidator dash:hasRootClass ;
>   sh:labelTemplate "Root class {$rootClass}" ;
>   sh:nodeValidator dash:hasRootClass ;
>   sh:parameter [
>       sh:predicate dash:rootClass ;
>       sh:class rdfs:Class ;
>       sh:description "The root class." ;
>       sh:name "root class" ;
>       sh:nodeKind sh:IRI ;
>     ] ;
>   sh:propertyValidator dash:hasRootClass ;
> .
> dash:SPARQLUpdateSuggestionGenerator
>   rdf:type rdfs:Class ;
>   rdfs:comment """A SuggestionGenerator based on a SPARQL UPDATE query
(sh:update), producing an instance of dash:GraphUpdate. The INSERTs become
dash:addedTriple and the DELETEs become dash:deletedTriple. The WHERE clause
operates on the data graph with the pre-bound variables $subject, $predicate
and $object, as well as the other pre-bound variables for the parameters of
the constraint.
>
> In many cases, there may be multiple possible suggestions to fix a problem.
For example, with sh:maxLength there are many ways to slice a string. In those
cases, the system will first iterate through the result variables from a
SELECT query (sh:select) and apply these results as pre-bound variables into
the UPDATE query.""" ;
>   rdfs:label "SPARQL UPDATE suggestion generator" ;
>   rdfs:subClassOf dash:SuggestionGenerator ;
>   rdfs:subClassOf sh:SPARQLSelectExecutable ;
>   rdfs:subClassOf sh:SPARQLUpdateExecutable ;
> .
> dash:SuccessResult
>   rdf:type rdfs:Class ;
>   rdfs:comment "A result representing a successfully validated constraint." ;
>   rdfs:label "Success result" ;
>   rdfs:subClassOf sh:AbstractResult ;
> .
> dash:SuccessTestCaseResult
>   rdf:type rdfs:Class ;
>   rdfs:comment "Represents a successful run of a test case." ;
>   rdfs:label "Success test case result" ;
>   rdfs:subClassOf dash:TestCaseResult ;
> .
> dash:Suggestion
>   rdf:type rdfs:Class ;
>   rdfs:comment "Base class of suggestions that modify a graph to \"fix\" the
source of a validation result." ;
>   rdfs:label "Suggestion" ;
>   rdfs:subClassOf rdfs:Resource ;
> .
> dash:SuggestionGenerator
>   rdf:type rdfs:Class ;
>   rdfs:comment "Base class of objects that can generate suggestions (added
or deleted triples) for a validation result of a given constraint component." ;
>   rdfs:label "Suggestion generator" ;
>   rdfs:subClassOf rdfs:Resource ;
> .
> dash:TestCase
>   rdf:type rdfs:Class ;
>   rdfs:comment "A test case to verify that a (SHACL-based) feature works as
expected." ;
>   rdfs:label "Test case" ;
>   rdfs:subClassOf rdfs:Resource ;
>   sh:abstract "true"^^xsd:boolean ;
> .
> dash:TestCaseResult
>   rdf:type rdfs:Class ;
>   rdf:type sh:Shape ;
>   rdfs:comment "Base class for results produced by running test cases." ;
>   rdfs:label "Test case result" ;
>   rdfs:subClassOf sh:AbstractResult ;
>   sh:property [
>       sh:predicate dash:testCase ;
>       sh:class dash:TestCase ;
>       sh:description "The dash:TestCase that was executed." ;
>       sh:maxCount 1 ;
>       sh:minCount 1 ;
>       sh:name "test case" ;
>     ] ;
>   sh:property [
>       sh:predicate dash:testGraph ;
>       sh:class rdfs:Resource ;
>       sh:description "The graph containing the test case." ;
>       sh:maxCount 1 ;
>       sh:minCount 1 ;
>       sh:name "test graph" ;
>       sh:nodeKind sh:IRI ;
>     ] ;
> .
> dash:ValidationTestCase
>   rdf:type rdfs:Class ;
>   rdf:type sh:Shape ;
>   rdfs:comment "Abstract superclass for test cases concerning SHACL
constraint validation. Future versions may add new kinds of validatin test
cases, e.g. to validate a single resource only." ;
>   rdfs:label "Validation test case" ;
>   rdfs:subClassOf dash:TestCase ;
>   sh:property [
>       sh:predicate dash:expectedResult ;
>       sh:class sh:AbstractResult ;
>       sh:description "The expected result of a function call." ;
>       sh:name "expected result" ;
>     ] ;
> .
> dash:addedTriple
>   rdf:type rdf:Property ;
>   rdfs:comment "May link a dash:GraphUpdate with one or more triples
(represented as instances of rdf:Statement) that should be added to fix the
source of the result." ;
>   rdfs:domain dash:GraphUpdate ;
>   rdfs:label "added triple" ;
>   rdfs:range rdf:Statement ;
> .
> dash:countShapesWithMatchResult
>   rdf:type sh:SPARQLFunction ;
>   rdfs:comment "Counts the number of shapes from a given rdf:List (?arg2)
defined in a given shapes graph (?arg3) where sh:hasShape returns the provided
match value (true or false, ?arg4) for a given focus node (?arg1). The
function produces a failure if one of the shapes validated to a failure." ;
>   rdfs:label "count shapes with match result" ;
>   sh:parameter [
>       sh:predicate sh:expectedValue ;
>       sh:datatype xsd:boolean ;
>       sh:description "The expected value of sh:hasShape to count." ;
>       sh:order 3 ;
>     ] ;
>   sh:parameter [
>       sh:predicate sh:focusNode ;
>       sh:class rdfs:Resource ;
>       sh:description "The focus node." ;
>       sh:order 0 ;
>     ] ;
>   sh:parameter [
>       sh:predicate sh:shapes ;
>       sh:class rdf:List ;
>       sh:description "The list of shapes to walk through." ;
>       sh:order 1 ;
>     ] ;
>   sh:parameter [
>       sh:predicate sh:shapesGraph ;
>       sh:class rdfs:Resource ;
>       sh:description "The shapes graph." ;
>       sh:order 2 ;
>     ] ;
>   sh:returnType xsd:integer ;
>   sh:select """
>   # 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 $shapesGraph {
>     $shapes rdf:rest*/rdf:first ?shape .
>    }
>    BIND (sh:hasShape($focusNode, ?shape, $shapesGraph, true) AS ?hasShape) .
>    BIND (IF(bound(?hasShape), IF(?hasShape = $expectedValue, 1, 0), 'error')
AS ?s) .
>   }
>   """ ;
> .
> dash:deletedTriple
>   rdf:type rdf:Property ;
>   rdfs:comment "May link a dash:GraphUpdate result with one or more triples
(represented as instances of rdf:Statement) that should be deleted to fix the
source of the result." ;
>   rdfs:domain dash:GraphUpdate ;
>   rdfs:label "deleted triple" ;
>   rdfs:range rdf:Statement ;
> .
> dash:hasClass
>   rdf:type sh:SPARQLAskValidator ;
>   rdfs:label "has class" ;
>   sh:ask """
>   ASK {
>    FILTER EXISTS { $value rdf:type/rdfs:subClassOf* $class }
>   }
>   """ ;
>   sh:message "Value does not have class {$class}" ;
> .
> dash:hasClassIn
>   rdf:type sh:SPARQLAskValidator ;
>   rdfs:comment "Checks whether a given node ($value) is an instance of a
class from a given list ($classIn). Uses the same logic as sh:hasClass for
each list member." ;
>   rdfs:label "has class in" ;
>   sh:ask """
>   ASK {
>    FILTER EXISTS {
>     GRAPH $shapesGraph {
>      $classIn (rdf:rest*)/rdf:first ?class .
>     }
>     FILTER dash:hasClass($value, ?class)
>    }
>   }
>   """ ;
>   sh:message "Value does not have one of the classes {$classIn}" ;
> .
> dash:hasDatatype
>   rdf:type sh:SPARQLAskValidator ;
>   rdfs:comment "Checks whether a given node ($value) is a literal with a
given datatype ($datatype)." ;
>   rdfs:label "has datatype" ;
>   sh:ask """
>   ASK {
>    {
>     FILTER isLiteral($value) .
>    } .
>    BIND (datatype($value) AS ?valueDatatype) .
>    FILTER (?valueDatatype = $datatype) .
>   }
>   """ ;
>   sh:message "Value does not have datatype {$datatype}" ;
> .
> dash:hasDatatypeIn
>   rdf:type sh:SPARQLAskValidator ;
>   rdfs:comment "Checks whether a given node ($value) is a literal with a
datatype from a given list ($datatypeIn). Uses the same logic as
sh:hasDatatype for each list member." ;
>   rdfs:label "has datatype in" ;
>   sh:ask """
>   ASK {
>    FILTER EXISTS {
>     GRAPH $shapesGraph {
>      $datatypeIn (rdf:rest*)/rdf:first ?datatype .
>     }
>     FILTER dash:hasDatatype($value, ?datatype)
>    }
>   }
>   """ ;
>   sh:message "Value does not have one of the datatypes {$datatypeIn}" ;
> .
> dash:hasMaxExclusive
>   rdf:type sh:SPARQLAskValidator ;
>   rdfs:comment "Checks whether a given node (?value) has value less than (<)
the provided ?maxExclusive. Returns false if this cannot be determined, e.g.
because values do not have comparable types." ;
>   rdfs:label "has max exclusive" ;
>   sh:ask "ASK { FILTER ($value < $maxExclusive) }" ;
>   sh:message "Value is not < {$maxExclusive}" ;
> .
> dash:hasMaxInclusive
>   rdf:type sh:SPARQLAskValidator ;
>   rdfs:comment "Checks whether a given node (?value) has value less than or
equal to (<=) the provided ?maxInclusive. Returns false if this cannot be
determined, e.g. because values do not have comparable types." ;
>   rdfs:label "has max inclusive" ;
>   sh:ask "ASK { FILTER ($value <= $maxInclusive) }" ;
>   sh:message "Value is not <= {$maxInclusive}" ;
> .
> dash:hasMaxLength
>   rdf:type sh:SPARQLAskValidator ;
>   rdfs:comment "Checks whether a given string (?value) has a length within a
given maximum string length." ;
>   rdfs:label "has max length" ;
>   sh:ask """
>   ASK {
>    BIND (STRLEN(str($value)) AS ?valueLength) .
>    FILTER (bound(?valueLength) && ?valueLength <= $maxLength) .
>   }
>   """ ;
>   sh:message "Value has more than {$maxLength} characters" ;
> .
> dash:hasMinExclusive
>   rdf:type sh:SPARQLAskValidator ;
>   rdfs:comment "Checks whether a given node (?value) has value greater than
(>) the provided ?minExclusive. Returns false if this cannot be determined,
e.g. because values do not have comparable types." ;
>   rdfs:label "has min exclusive" ;
>   sh:ask "ASK { FILTER ($value > $minExclusive) }" ;
>   sh:message "Value is not > {$minExclusive}" ;
> .
> dash:hasMinInclusive
>   rdf:type sh:SPARQLAskValidator ;
>   rdfs:comment "Checks whether a given node (?value) has value greater than
or equal to (>=) the provided ?minInclusive. Returns false if this cannot be
determined, e.g. because values do not have comparable types." ;
>   rdfs:label "has min inclusive" ;
>   sh:ask "ASK { FILTER ($value >= $minInclusive) }" ;
>   sh:message "Value is not >= {$minInclusive}" ;
> .
> dash:hasMinLength
>   rdf:type sh:SPARQLAskValidator ;
>   rdfs:comment "Checks whether a given string (?value) has a length within a
given minimum string length." ;
>   rdfs:label "has min length" ;
>   sh:ask """
>   ASK {
>    BIND (STRLEN(str($value)) AS ?valueLength) .
>    FILTER (bound(?valueLength) && ?valueLength >= $minLength) .
>   }
>   """ ;
>   sh:message "Value has less than {$minLength} characters" ;
> .
> dash:hasNodeKind
>   rdf:type sh:SPARQLAskValidator ;
>   rdfs:comment "Checks whether a given node (?value) has a given sh:NodeKind
(?nodeKind). For example, sh:hasNodeKind(42, sh:Literal) = true." ;
>   rdfs:label "has node kind" ;
>   sh:ask """
>   ASK {
>    FILTER ((isIRI($value) && $nodeKind IN ( sh:IRI, sh:BlankNodeOrIRI,
sh:IRIOrLiteral ) ) ||
>     (isLiteral($value) && $nodeKind IN ( sh:Literal, sh:BlankNodeOrLiteral,
sh:IRIOrLiteral ) ) ||
>     (isBlank($value)   && $nodeKind IN ( sh:BlankNode, sh:BlankNodeOrIRI,
sh:BlankNodeOrLiteral ) )) .
>   }
>   """ ;
>   sh:message "Value has not node kind {$nodeKind}" ;
> .
> dash:hasPattern
>   rdf:type sh:SPARQLAskValidator ;
>   rdfs:comment "Checks whether the string representation of a given node
(?value) matches a given regular expression (?pattern). Returns false if the
value is a blank node." ;
>   rdfs:label "has pattern" ;
>   sh:ask "ASK { FILTER (!isBlank($value) && IF(bound($flags),
regex(str($value), $pattern, $flags), regex(str($value), $pattern))) }" ;
>   sh:message "Value does not match pattern \"{$pattern}\"" ;
> .
> dash:hasRootClass
>   rdf:type sh:SPARQLAskValidator ;
>   rdfs:label "has root class" ;
>   sh:ask """ASK {
>     $value rdfs:subClassOf* $rootClass .
> }""" ;
> .
> dash:hasStem
>   rdf:type sh:SPARQLAskValidator ;
>   rdfs:comment "Checks whether a given node is an IRI starting with a given
stem." ;
>   rdfs:label "has stem" ;
>   sh:ask "ASK { FILTER (isIRI($value) && STRSTARTS(str($value), $stem)) }" ;
>   sh:message "Value does not have stem {$stem}" ;
> .
> dash:inversePropertySuggestionGenerator
>   rdf:type rdf:Property ;
>   rdfs:comment "Links the constraint component with instances of
dash:SuggestionGenerator that may be used to produce suggestions for a given
validation result that was produced by an inverse property constraint." ;
>   rdfs:domain sh:ConstraintComponent ;
>   rdfs:label "inverse property suggestion generator" ;
>   rdfs:range dash:SuggestionGenerator ;
> .
> dash:inverseValueCount
>   rdf:type sh:SPARQLFunction ;
>   rdfs:comment "Computes the number of subjects for a given object/predicate
combination." ;
>   rdfs:label "inverse value count" ;
>   sh:parameter [
>       sh:predicate dash:object ;
>       sh:class rdfs:Resource ;
>       sh:description "The object to get the number of subjects of." ;
>       sh:name "object" ;
>       sh:order 0 ;
>     ] ;
>   sh:parameter [
>       sh:predicate dash:predicate ;
>       sh:class rdfs:Resource ;
>       sh:description "The predicate to get the number of subjects of." ;
>       sh:name "predicate" ;
>       sh:order 1 ;
>     ] ;
>   sh:returnType xsd:integer ;
>   sh:select """
>   SELECT (COUNT(?subject) AS ?result)
>   WHERE {
>       ?subject $predicate $object .
>   }
> """ ;
> .
> dash:isDeactivated
>   rdf:type sh:SPARQLFunction ;
>   rdfs:comment "Checks whether a given shape or constraint has been marked
as \"deactivated\". This relies on the coding convention to have
sh:filterShape dash:None." ;
>   sh:ask """ASK {
>     ?constraintOrShape sh:filterShape dash:None .
> }""" ;
>   sh:parameter [
>       sh:predicate dash:constraintOrShape ;
>       sh:description "The sh:Constraint or sh:Shape to test." ;
>       sh:name "constraint or shape" ;
>     ] ;
>   sh:returnType xsd:boolean ;
> .
> dash:isIn
>   rdf:type sh:SPARQLAskValidator ;
>   sh:ask """
>   ASK {
>    FILTER EXISTS {
>     GRAPH $shapesGraph {
>      $in (rdf:rest*)/rdf:first $value .
>     }
>    }
>   }
>   """ ;
>   sh:message "Value is not in {$in}" ;
> .
> dash:isNodeKindBlankNode
>   rdf:type sh:SPARQLFunction ;
>   dash:cachable "true"^^xsd:boolean ;
>   rdfs:comment "Checks if a given sh:NodeKind is one that includes
BlankNodes." ;
>   rdfs:label "is NodeKind BlankNode" ;
>   sh:ask """ASK {
>  FILTER ($nodeKind IN ( sh:BlankNode, sh:BlankNodeOrIRI,
sh:BlankNodeOrLiteral ))
> }""" ;
>   sh:parameter [
>       sh:predicate dash:nodeKind ;
>       sh:class sh:NodeKind ;
>       sh:description "The sh:NodeKind to check." ;
>       sh:name "node kind" ;
>       sh:nodeKind sh:IRI ;
>     ] ;
>   sh:returnType xsd:boolean ;
> .
> dash:isNodeKindIRI
>   rdf:type sh:SPARQLFunction ;
>   dash:cachable "true"^^xsd:boolean ;
>   rdfs:comment "Checks if a given sh:NodeKind is one that includes IRIs." ;
>   rdfs:label "is NodeKind IRI" ;
>   sh:ask """ASK {
>  FILTER ($nodeKind IN ( sh:IRI, sh:BlankNodeOrIRI, sh:IRIOrLiteral ))
> }""" ;
>   sh:parameter [
>       sh:predicate dash:nodeKind ;
>       sh:class sh:NodeKind ;
>       sh:description "The sh:NodeKind to check." ;
>       sh:name "node kind" ;
>       sh:nodeKind sh:IRI ;
>     ] ;
>   sh:returnType xsd:boolean ;
> .
> dash:isNodeKindLiteral
>   rdf:type sh:SPARQLFunction ;
>   dash:cachable "true"^^xsd:boolean ;
>   rdfs:comment "Checks if a given sh:NodeKind is one that includes Literals." ;
>   rdfs:label "is NodeKind Literal" ;
>   sh:ask """ASK {
>  FILTER ($nodeKind IN ( sh:Literal, sh:BlankNodeOrLiteral, sh:IRIOrLiteral ))
> }""" ;
>   sh:parameter [
>       sh:predicate dash:nodeKind ;
>       sh:class sh:NodeKind ;
>       sh:description "The sh:NodeKind to check." ;
>       sh:name "node kind" ;
>       sh:nodeKind sh:IRI ;
>     ] ;
>   sh:returnType xsd:boolean ;
> .
> dash:propertySuggestionGenerator
>   rdf:type rdf:Property ;
>   rdfs:comment "Links the constraint component with instances of
dash:SuggestionGenerator that may be used to produce suggestions for a given
validation result that was produced by a property constraint." ;
>   rdfs:domain sh:ConstraintComponent ;
>   rdfs:label "property suggestion generator" ;
>   rdfs:range dash:SuggestionGenerator ;
> .
> dash:suggestion
>   rdf:type rdf:Property ;
>   rdfs:comment "Can be used to link a validation result with one or more
suggestions on how to fix the underlying issue." ;
>   rdfs:domain sh:ValidationResult ;
>   rdfs:label "suggestion" ;
>   rdfs:range dash:Suggestion ;
> .
> dash:suggestionGenerator
>   rdf:type rdf:Property ;
>   rdfs:comment "Links a sh:SPARQLConstraint with instances of
dash:SuggestionGenerator that may be used to produce suggestions for a given
validation result that was produced by the constraint." ;
>   rdfs:domain sh:SPARQLConstraint ;
>   rdfs:label "suggestion generator" ;
>   rdfs:range dash:SuggestionGenerator ;
> .
> dash:valueCount
>   rdf:type sh:SPARQLFunction ;
>   rdfs:comment "Computes the number of objects for a given subject/predicate
combination." ;
>   rdfs:label "value count" ;
>   sh:parameter [
>       sh:predicate dash:predicate ;
>       sh:class rdfs:Resource ;
>       sh:description "The predicate to get the number of objects of." ;
>       sh:name "predicate" ;
>       sh:order 1 ;
>     ] ;
>   sh:parameter [
>       sh:predicate dash:subject ;
>       sh:class rdfs:Resource ;
>       sh:description "The subject to get the number of objects of." ;
>       sh:name "subject" ;
>       sh:order 0 ;
>     ] ;
>   sh:returnType xsd:integer ;
>   sh:select """
>   SELECT (COUNT(?object) AS ?result)
>   WHERE {
>       $subject $predicate ?object .
>   }
> """ ;
> .
> dash:valuesWithShapeCount
>   rdf:type sh:SPARQLFunction ;
>   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." ;
>   rdfs:label "values with shape count" ;
>   sh:parameter [
>       sh:predicate sh:arg1 ;
>       sh:class rdfs:Resource ;
>       sh:description "The subject to count the values of." ;
>     ] ;
>   sh:parameter [
>       sh:predicate sh:arg2 ;
>       sh:class rdf:Property ;
>       sh:description "The property to count the values of." ;
>     ] ;
>   sh:parameter [
>       sh:predicate sh:arg3 ;
>       sh:class sh:Shape ;
>       sh:description "The shape to validate." ;
>     ] ;
>   sh:parameter [
>       sh:predicate sh:arg4 ;
>       sh:class rdfs:Resource ;
>       sh:description "The shapes graph." ;
>     ] ;
>   sh:returnType xsd:integer ;
>   sh:select """
>   # 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 {
>    {
>     FILTER NOT EXISTS { $arg1 $arg2 ?value }
>     BIND (0 AS ?s)
>    }
>    UNION {
>     FILTER EXISTS { $arg1 $arg2 ?value }
>     $arg1 $arg2 ?value .
>     BIND (sh:hasShape(?value, $arg3, $arg4, true) AS ?hasShape) .
>     BIND (IF(bound(?hasShape), IF(?hasShape, 1, 0), 'error') AS ?s) .
>    }
>   }
>   """ ;
> .
> owl:Class
>   rdf:type rdfs:Class ;
>   rdfs:subClassOf rdfs:Class ;
> .
> sh:AndConstraintComponent
>   sh:inversePropertyValidator [
>       rdf:type sh:SPARQLSelectValidator ;
>       sh:select """
>   SELECT $this ($this AS ?object) $predicate (?value AS ?subject) ?failure
>   WHERE {
>    ?value $predicate $this .
>    BIND (dash:countShapesWithMatchResult(?value, $and, $shapesGraph, false)
AS ?count)
>    BIND (!bound(?count) AS ?failure) .
>    FILTER IF(?failure, true, ?count > 0) .
>   }
> """ ;
>     ] ;
>   sh:nodeValidator [
>       rdf:type sh:SPARQLSelectValidator ;
>       sh:select """
>   SELECT $this ?failure
>   WHERE {
>    BIND (dash:countShapesWithMatchResult($this, $and, $shapesGraph, false)
AS ?count)
>    BIND (!bound(?count) AS ?failure) .
>    FILTER IF(?failure, true, ?count > 0) .
>   }
> """ ;
>     ] ;
>   sh:propertyValidator [
>       rdf:type sh:SPARQLSelectValidator ;
>       sh:select """
>   SELECT $this ($this AS ?subject) $predicate (?value AS ?object) ?failure
>   WHERE {
>    $this $predicate ?value .
>    BIND (dash:countShapesWithMatchResult(?value, $and, $shapesGraph, false)
AS ?count)
>    BIND (!bound(?count) AS ?failure) .
>    FILTER IF(?failure, true, ?count > 0) .
>   }
> """ ;
>     ] ;
> .
> sh:ClassConstraintComponent
>   sh:inversePropertyValidator dash:hasClass ;
>   sh:nodeValidator dash:hasClass ;
>   sh:propertyValidator dash:hasClass ;
> .
> sh:ClassInConstraintComponent
>   sh:inversePropertyValidator dash:hasClassIn ;
>   sh:nodeValidator dash:hasClassIn ;
>   sh:propertyValidator dash:hasClassIn ;
> .
> sh:ClosedConstraintComponent
>   sh:nodeValidator [
>       rdf:type sh:SPARQLSelectValidator ;
>       sh:message "Predicate {?predicate} is not allowed (closed shape)" ;
>       sh:select """
>   SELECT $this ($this AS ?subject) ?predicate ?object
>   WHERE {
>    {
>     FILTER ($closed) .
>    }
>    $this ?predicate ?object .
>    FILTER (NOT EXISTS {
>     GRAPH $shapesGraph {
>      $currentShape sh:property/sh:predicate ?predicate .
>     }
>    } && (!bound($ignoredProperties) || NOT EXISTS {
>     GRAPH $shapesGraph {
>      $ignoredProperties rdf:rest*/rdf:first ?predicate .
>     }
>    }))
>   }
> """ ;
>     ] ;
> .
> sh:DatatypeConstraintComponent
>   sh:nodeValidator dash:hasDatatype ;
>   sh:propertyValidator dash:hasDatatype ;
> .
> sh:DatatypeInConstraintComponent
>   sh:nodeValidator dash:hasDatatypeIn ;
>   sh:propertyValidator dash:hasDatatypeIn ;
> .
> sh:DisjointConstraintComponent
>   sh:propertyValidator [
>       rdf:type sh:SPARQLSelectValidator ;
>       sh:message "Property must not share any values with {$disjoint}" ;
>       sh:select """
>   SELECT $this ($this AS ?subject) $predicate ?object
>   WHERE {
>    $this $predicate ?object .
>    $this $disjoint ?object .
>   }
>   """ ;
>     ] ;
> .
> sh:EqualsConstraintComponent
>   sh:propertyValidator [
>       rdf:type sh:SPARQLSelectValidator ;
>       sh:message "Property must have same values as {$equals}" ;
>       sh:select """
>   SELECT $this ($this AS ?subject) $predicate ?object
>   WHERE {
>    {
>     $this $predicate ?object .
>     FILTER NOT EXISTS {
>      $this $equals ?object .
>     }
>    }
>    UNION
>    {
>     $this $equals ?object .
>     FILTER NOT EXISTS {
>      $this $predicate ?object .
>     }
>    }
>   }
>   """ ;
>     ] ;
> .
> sh:Function
>   sh:property [
>       sh:predicate dash:cachable ;
>       sh:datatype xsd:boolean ;
>       sh:description "True to indicate that this function will always return
the same values for the same combination of arguments, regardless of the query
graphs. Engines can use this information to cache and reuse previous function
calls." ;
>       sh:maxCount 1 ;
>       sh:name "cachable" ;
>     ] ;
> .
> sh:HasValueConstraintComponent
>   sh:inversePropertyValidator [
>       rdf:type sh:SPARQLSelectValidator ;
>       sh:message "Property does not have inverse value {$hasValue}" ;
>       sh:select """
>   SELECT $this ($this AS ?object) $predicate
>   WHERE {
>    FILTER NOT EXISTS { $hasValue $predicate $this }
>   }
>   """ ;
>     ] ;
>   sh:propertyValidator [
>       rdf:type sh:SPARQLSelectValidator ;
>       sh:message "Property does not have value {$hasValue}" ;
>       sh:select """
>   SELECT $this ($this AS ?subject) $predicate
>   WHERE {
>    FILTER NOT EXISTS { $this $predicate $hasValue }

Received on Sunday, 5 June 2016 20:10:48 UTC