Skip to content

Commit 086b260

Browse files
committed
added parsing of the cypher in sh:targetQuery
1 parent a01734e commit 086b260

File tree

3 files changed

+87
-7
lines changed

3 files changed

+87
-7
lines changed

src/main/java/n10s/validation/SHACLValidator.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@
2222
import org.eclipse.rdf4j.rio.RDFFormat;
2323
import org.eclipse.rdf4j.rio.helpers.BasicParserSettings;
2424
import org.eclipse.rdf4j.sail.memory.MemoryStore;
25+
import org.neo4j.graphdb.GraphDatabaseService;
2526
import org.neo4j.graphdb.Transaction;
2627
import org.neo4j.logging.Log;
2728

2829
import java.io.IOException;
2930
import java.io.InputStream;
3031
import java.io.InputStreamReader;
3132
import java.util.*;
33+
import java.util.concurrent.TimeUnit;
3234

3335
import static n10s.graphconfig.GraphConfig.*;
3436
import static n10s.graphconfig.Params.WKTLITERAL_URI;
@@ -52,6 +54,7 @@ public class SHACLValidator {
5254
public static final String SHACL_VALUE_RANGE_CONSTRAINT_COMPONENT = "http://www.w3.org/ns/shacl#ValueRangeConstraintComponent";
5355
public static final String SHACL_LENGTH_CONSTRAINT_COMPONENT = "http://www.w3.org/ns/shacl#LengthConstraintComponent";
5456

57+
5558
String PROP_CONSTRAINT_QUERY = "prefix sh: <http://www.w3.org/ns/shacl#> \n" +
5659
"SELECT distinct ?ns ?ps ?path ?invPath ?rangeClass ?rangeKind ?datatype ?severity (coalesce(?pmsg, ?nmsg,\"\") as ?msg)\n" +
5760
"?targetClass ?targetIsQuery ?pattern ?maxCount ?minCount ?minInc ?minExc ?maxInc ?maxExc ?minStrLen \n" +
@@ -169,12 +172,15 @@ public class SHACLValidator {
169172
"} group by ?ns ?nmsg ?targetClass ?targetIsQuery";
170173

171174
private Transaction tx;
175+
private GraphDatabaseService db;
172176
private Log log;
173177
private GraphConfig gc;
174178

175-
public SHACLValidator(Transaction transaction, Log l) {
179+
public SHACLValidator(GraphDatabaseService db, Transaction transaction, Log l) {
176180
this.tx = transaction;
177181
this.log = l;
182+
this.db = db;
183+
178184
try {
179185
this.gc = new GraphConfig(tx);
180186
} catch (GraphConfigNotFound graphConfigNotFound) {
@@ -186,7 +192,7 @@ public SHACLValidator(Transaction transaction, Log l) {
186192

187193

188194
protected ValidatorConfig compileValidations(Iterator<Map<String, Object>> constraints)
189-
throws InvalidNamespacePrefixDefinitionInDB, UriNamespaceHasNoAssociatedPrefix {
195+
throws InvalidNamespacePrefixDefinitionInDB, UriNamespaceHasNoAssociatedPrefix {
190196

191197
ValidatorConfig vc = new ValidatorConfig();
192198

@@ -206,7 +212,7 @@ protected ValidatorConfig compileValidations(Iterator<Map<String, Object>> const
206212
}
207213

208214
protected void processConstraint(Map<String, Object> theConstraint, ValidatorConfig vc)
209-
throws InvalidNamespacePrefixDefinitionInDB, UriNamespaceHasNoAssociatedPrefix {
215+
throws InvalidNamespacePrefixDefinitionInDB, UriNamespaceHasNoAssociatedPrefix {
210216

211217
int constraintType;
212218
String focusLabel = "";
@@ -223,6 +229,7 @@ protected void processConstraint(Map<String, Object> theConstraint, ValidatorCon
223229
} else if (theConstraint.containsKey("appliesToQueryResult") ){
224230
//it's a query based constraint
225231
whereClause = (String) theConstraint.get("appliesToQueryResult");
232+
validateWhereClause(whereClause);
226233
constraintType = QUERY_BASED_CONSTRAINT;
227234
} else {
228235
throw new SHACLValidationException("invalid constraint config");
@@ -843,6 +850,18 @@ protected void processConstraint(Map<String, Object> theConstraint, ValidatorCon
843850

844851
}
845852

853+
private void validateWhereClause(String whereClause) {
854+
try (Transaction tempTransaction = db.beginTx(50, TimeUnit.MILLISECONDS)) {
855+
856+
tempTransaction.execute("explain " + "match (focus) where " + whereClause + " return focus limit 1 ");
857+
} catch (Exception e){
858+
throw new SHACLValidationException("Invalid cypher expression: \"" + whereClause + "\". The cypher fragment " +
859+
"in a sh:targetQuery element should form a valid query when embeeded in the following template: " +
860+
" \"match (focus) where <your cypher> return focus\"");
861+
}
862+
863+
}
864+
846865
private String[] buildArgArray(int constraintType, List<String> classBasedParamList, List<String> queryBasedParamList) {
847866
List<String> argsAsList = new ArrayList<>();
848867
argsAsList = constraintType == CLASS_BASED_CONSTRAINT ? classBasedParamList : queryBasedParamList;

src/main/java/n10s/validation/ValidationProcedures.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ public Stream<ValidationResult> shaclValidateTxForTrigger(
5959
public Stream<ConstraintComponent> importInlineSHACL(@Name("rdf") String rdfFragment,
6060
@Name("format") String format,
6161
@Name(value = "params", defaultValue = "{}") Map<String, Object> props)
62-
throws IOException, RDFImportBadParams, InvalidNamespacePrefixDefinitionInDB, UriNamespaceHasNoAssociatedPrefix {
62+
throws IOException, RDFImportBadParams, InvalidNamespacePrefixDefinitionInDB, UriNamespaceHasNoAssociatedPrefix
63+
{
6364

6465
return doLoad(format, null, rdfFragment, props).stream();
6566
}
@@ -69,15 +70,17 @@ public Stream<ConstraintComponent> importInlineSHACL(@Name("rdf") String rdfFrag
6970
public Stream<ConstraintComponent> importSHACLFromURL(@Name("url") String url,
7071
@Name("format") String format,
7172
@Name(value = "params", defaultValue = "{}") Map<String, Object> props)
72-
throws IOException, RDFImportBadParams, InvalidNamespacePrefixDefinitionInDB, UriNamespaceHasNoAssociatedPrefix {
73+
throws IOException, RDFImportBadParams, InvalidNamespacePrefixDefinitionInDB, UriNamespaceHasNoAssociatedPrefix
74+
{
7375

7476
return doLoad(format, url, null, props).stream();
7577

7678
}
7779

7880
private List<ConstraintComponent> doLoad(String format, String url, String rdfFragment,
7981
Map<String, Object> props)
80-
throws IOException, RDFImportBadParams, InvalidNamespacePrefixDefinitionInDB, UriNamespaceHasNoAssociatedPrefix {
82+
throws IOException, RDFImportBadParams, InvalidNamespacePrefixDefinitionInDB, UriNamespaceHasNoAssociatedPrefix
83+
{
8184

8285
InputStream is;
8386
if (rdfFragment != null) {
@@ -86,7 +89,7 @@ private List<ConstraintComponent> doLoad(String format, String url, String rdfFr
8689
is = getInputStream(url, props);
8790
}
8891

89-
SHACLValidator validator = new SHACLValidator(tx, log);
92+
SHACLValidator validator = new SHACLValidator(db, tx, log);
9093
ValidatorConfig validatorConfig = validator
9194
.compileValidations(validator.parseConstraints(is, getFormat(format), props));
9295

src/test/java/n10s/validation/SHACLValidationProceduresTest.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1845,6 +1845,64 @@ public void testRequiredAndExcludedTypes() throws Exception {
18451845
}
18461846
}
18471847

1848+
String SHAPES_BAD_QUERY_1 = "@prefix ex: <http://example.neo4j.com/graphvalidation#> .\n" +
1849+
"@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
1850+
"@prefix neo4j: <neo4j://graph.schema#> .\n" +
1851+
"@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n" +
1852+
"@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n" +
1853+
"\n" +
1854+
"ex:shape01 a sh:NodeShape ;\n" +
1855+
" sh:targetQuery \" (focus)-[:daughter_of]->(y) \" ;\n" +
1856+
" sh:class neo4j:Pointless ;\n" +
1857+
".\n" ;
1858+
1859+
String SHAPES_BAD_QUERY_2 = "@prefix ex: <http://example.neo4j.com/graphvalidation#> .\n" +
1860+
"@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
1861+
"@prefix neo4j: <neo4j://graph.schema#> .\n" +
1862+
"@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n" +
1863+
"@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n" +
1864+
"\n" +
1865+
"ex:shape01 a sh:NodeShape ;\n" +
1866+
" sh:targetQuery \" true return count(*); create (:HelloThere) ; \" ;\n" +
1867+
" sh:class neo4j:Pointless ;\n" +
1868+
".\n" ;
1869+
1870+
String SHAPES_BAD_QUERY_3 = "@prefix ex: <http://example.neo4j.com/graphvalidation#> .\n" +
1871+
"@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
1872+
"@prefix neo4j: <neo4j://graph.schema#> .\n" +
1873+
"@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n" +
1874+
"@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n" +
1875+
"\n" +
1876+
"ex:shape01 a sh:NodeShape ;\n" +
1877+
" sh:targetQuery \" where existis(focus.prop) \" ;\n" +
1878+
" sh:class neo4j:Pointless ;\n" +
1879+
".\n" ;
1880+
1881+
@Test
1882+
public void testBadQueriesInConstraint() throws Exception {
1883+
verifyBadCypher(SHAPES_BAD_QUERY_1);
1884+
verifyBadCypher(SHAPES_BAD_QUERY_2);
1885+
verifyBadCypher(SHAPES_BAD_QUERY_3);
1886+
}
1887+
1888+
private void verifyBadCypher(String query) {
1889+
try (Driver driver = GraphDatabase.driver(neo4j.boltURI(),
1890+
Config.builder().withoutEncryption().build()); Session session = driver.session()) {
1891+
1892+
Result results = session
1893+
.run("CALL n10s.validation.shacl.import.inline('" + query + "',\"Turtle\")");
1894+
1895+
results.hasNext();
1896+
1897+
//should never get here
1898+
assertTrue(false);
1899+
1900+
} catch (Exception e){
1901+
assertTrue(e.getMessage().contains("The cypher fragment in a sh:targetQuery element " +
1902+
"should form a valid query when embeeded in the following template"));
1903+
}
1904+
}
1905+
18481906
private boolean contains(Set<ValidationResult> set, ValidationResult res) {
18491907
boolean contained = false;
18501908
for (ValidationResult vr : set) {

0 commit comments

Comments
 (0)