shacl2cypher
← Shapes for Property Graphs

5. Constraining values

Once you know how many values there are, the next question is what they may be: the right type, in range, the right shape of string, one of an allowed list.

All examples are in 05-values.ttl, which compiles to 12 rules.

Datatypes

sh:property [ sh:path ex:age ; sh:datatype xsd:integer ] ;

SHACL uses XML Schema datatypes. shacl2cypher maps each one to the types your database actually stores:

DatatypeNeo4jLadybugDB column
xsd:stringSTRINGSTRING
xsd:integerINTEGERany integer type
xsd:int, xsd:short, xsd:byteINTEGER plus a range checkINT32/INT16/INT8
xsd:decimal, xsd:doubleFLOATDECIMAL/DOUBLE/FLOAT
xsd:booleanBOOLEANBOOL
xsd:dateDATEDATE
xsd:dateTimeZONED DATETIME or LOCAL DATETIMETIMESTAMP
xsd:durationDURATIONINTERVAL

Need exactly one native type, such as only zoned date-times? Add s2c:datatype "ZONED_DATETIME" to the property shape.

On LadybugDB the schema often answers for you. If age is an INT64 column, sh:datatype xsd:integer can never fail, so the rule is guaranteed-by-schema and runs no query. If age is a STRING column, the rule can never pass: that is reported at compile time as a schema mismatch.

Ranges

sh:property [
    sh:path ex:age ;
    sh:datatype xsd:integer ;
    sh:minInclusive 0 ;
    sh:maxInclusive 130 ;
] ;
sh:property [
    sh:path ex:hiredOn ;
    sh:datatype xsd:date ;
    sh:minInclusive "2000-01-01"^^xsd:date ;
] .

sh:minInclusive, sh:maxInclusive, sh:minExclusive and sh:maxExclusive compare each value with a typed constant. Dates compare with dates, numbers with numbers.

A wrong type is always a violation

What if age holds the string "forty"? In Cypher, "forty" >= 0 is null, and a naive WHERE NOT (p.age >= 0) would silently skip the row. shacl2cypher never lets that happen. Every value test is wrapped so that "can't compare" counts as "fails", and the row is reported with value: "forty". That is also what SHACL requires.

The flip side: value constraints ignore missing values. A person with no age passes every rule above. If age is required, add sh:minCount 1.

String length

sh:property [ sh:path ex:name ; sh:datatype xsd:string ; sh:minLength 2 ; sh:maxLength 80 ] ;

Lengths apply to strings, and to integers by their decimal digits. Any other value type fails a length constraint.

Patterns

sh:property [ sh:path ex:email ; sh:pattern "^[^@\\s]+@[^@\\s]+$" ] ;
sh:property [ sh:path ex:code  ; sh:pattern "^emp-[0-9]{4}$" ; sh:flags "i" ] ;

Three things to know about sh:pattern:

  1. It matches anywhere in the string unless you anchor it. "[0-9]" passes "abc1". Use ^ and $ for a whole-string match.
  2. Backslashes are doubled inside a Turtle string: \\s in the file is \s in the regex.
  3. Flags: i ignores case, s lets . match newlines, m makes ^/$ match at lines, x ignores whitespace in the pattern, and q treats the pattern as a literal string.

Patterns are XSD regular expressions. shacl2cypher parses each one and rewrites it for the engine, for example wrapping it as (?s:.*)(?:…)(?s:.*) for Neo4j's anchored =~. An invalid pattern is a compile error at its line in your file. Back-references such as (a)\1 work on Neo4j but are rejected for LadybugDB, whose regex engine can't evaluate them.

Allowed values

sh:property [ sh:path ex:status ; sh:in ( "active" "on-leave" "left" ) ] ;

Every value must be one of the list. IRIs in the list, such as ( ex:Active ex:Left ), compare by local name: "Active" and "Left".

A required value

ex:CompanyShape a sh:NodeShape ;
    sh:targetClass ex:Company ;
    sh:property [ sh:path ex:markets ; sh:hasValue "EU" ] .

sh:hasValue means "one of the values is this". It is the only value constraint besides counts that fails on an empty set. A company with no markets violates it.

Per-value results

Value constraints report one row per bad value, so a list with two bad elements gives two rows, each with its value:

failed      Warning             1         3  PersonShape.name.pattern
            - Person id=p1: ex:PersonShape violates sh:pattern on ex:name (value: "ann")

Takeaways

  • sh:datatype maps XSD types onto native graph types; use s2c:datatype to demand an exact one.
  • A value of the wrong type fails every value test. A missing value passes them all.
  • Patterns match substrings unless anchored, and are validated at compile time.
  • sh:in restricts values to a list; sh:hasValue requires one particular value.