Skip to content

Commit 86cbf5b

Browse files
authored
GH-4686 support ?failure var in SparqlConstraint select queries (#4807)
2 parents f177ad4 + b35e6f2 commit 86cbf5b

4 files changed

Lines changed: 232 additions & 3 deletions

File tree

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2023 Eclipse RDF4J contributors.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
******************************************************************************/
11+
12+
package org.eclipse.rdf4j.sail.shacl.ast;
13+
14+
import java.util.Arrays;
15+
16+
import org.eclipse.rdf4j.common.exception.RDF4JException;
17+
import org.eclipse.rdf4j.model.Resource;
18+
import org.eclipse.rdf4j.model.Value;
19+
import org.eclipse.rdf4j.query.BindingSet;
20+
21+
/**
22+
* An exception thrown when the ?failure var is true for a SPARQL constraint select query.
23+
*/
24+
public class ShaclSparqlConstraintFailureException extends RDF4JException {
25+
26+
private final Shape shape;
27+
private final String query;
28+
private final BindingSet resultBindingSet;
29+
private final Value focusNode;
30+
private final Resource[] dataGraph;
31+
32+
public ShaclSparqlConstraintFailureException(Shape shape, String query, BindingSet resultBindingSet,
33+
Value focusNode, Resource[] dataGraph) {
34+
super("The ?failure variable was true for " + valueToString(focusNode) + " in shape "
35+
+ resourceToString(shape.getId()) + " with result resultBindingSet: " + resultBindingSet.toString()
36+
+ " and dataGraph: " + Arrays.toString(dataGraph) + " and query:" + query);
37+
this.shape = shape;
38+
this.query = query;
39+
this.resultBindingSet = resultBindingSet;
40+
this.focusNode = focusNode;
41+
this.dataGraph = dataGraph;
42+
}
43+
44+
public String getShape() {
45+
return shape.toString();
46+
}
47+
48+
public String getQuery() {
49+
return query;
50+
}
51+
52+
public BindingSet getResultBindingSet() {
53+
return resultBindingSet;
54+
}
55+
56+
public Value getFocusNode() {
57+
return focusNode;
58+
}
59+
60+
public Resource[] getDataGraph() {
61+
return dataGraph;
62+
}
63+
64+
private static String resourceToString(Resource id) {
65+
assert id != null;
66+
if (id == null) {
67+
return "null";
68+
}
69+
if (id.isIRI()) {
70+
return "<" + id.stringValue() + ">";
71+
}
72+
if (id.isBNode()) {
73+
return id.toString();
74+
}
75+
if (id.isTriple()) {
76+
return "TRIPLE " + id;
77+
}
78+
return id.toString();
79+
}
80+
81+
private static String valueToString(Value value) {
82+
assert value != null;
83+
if (value == null) {
84+
return "null";
85+
}
86+
if (value.isResource()) {
87+
return resourceToString((Resource) value);
88+
}
89+
if (value.isLiteral()) {
90+
return value.toString();
91+
}
92+
return value.toString();
93+
}
94+
}

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/Shape.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ abstract public class Shape implements ConstraintComponent, Identifiable {
9797
private static final Logger logger = LoggerFactory.getLogger(Shape.class);
9898
protected boolean produceValidationReports;
9999

100-
Resource id;
100+
private Resource id;
101101
TargetChain targetChain;
102102

103103
List<Target> target = new ArrayList<>();

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/SparqlConstraintSelect.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
1818
import org.eclipse.rdf4j.model.Resource;
1919
import org.eclipse.rdf4j.model.Value;
20+
import org.eclipse.rdf4j.model.impl.BooleanLiteral;
2021
import org.eclipse.rdf4j.query.BindingSet;
2122
import org.eclipse.rdf4j.query.Dataset;
2223
import org.eclipse.rdf4j.query.MalformedQueryException;
@@ -28,8 +29,10 @@
2829
import org.eclipse.rdf4j.sail.SailConnection;
2930
import org.eclipse.rdf4j.sail.SailException;
3031
import org.eclipse.rdf4j.sail.memory.MemoryStoreConnection;
32+
import org.eclipse.rdf4j.sail.shacl.ast.ShaclSparqlConstraintFailureException;
3133
import org.eclipse.rdf4j.sail.shacl.ast.Shape;
3234
import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.ConstraintComponent;
35+
import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.SparqlConstraintComponent;
3336
import org.eclipse.rdf4j.sail.shacl.results.ValidationResult;
3437
import org.slf4j.Logger;
3538
import org.slf4j.LoggerFactory;
@@ -47,7 +50,7 @@ public class SparqlConstraintSelect implements PlanNode {
4750
private final String query;
4851
private final Resource[] dataGraph;
4952
private final boolean produceValidationReports;
50-
private final ConstraintComponent constraintComponent;
53+
private final SparqlConstraintComponent constraintComponent;
5154
private final Shape shape;
5255
private final String[] variables;
5356
private final ConstraintComponent.Scope scope;
@@ -58,7 +61,7 @@ public class SparqlConstraintSelect implements PlanNode {
5861

5962
public SparqlConstraintSelect(SailConnection connection, PlanNode targets, String query,
6063
ConstraintComponent.Scope scope,
61-
Resource[] dataGraph, boolean produceValidationReports, ConstraintComponent constraintComponent,
64+
Resource[] dataGraph, boolean produceValidationReports, SparqlConstraintComponent constraintComponent,
6265
Shape shape) {
6366
this.connection = connection;
6467
this.targets = targets;
@@ -113,6 +116,13 @@ private void calculateNext() {
113116

114117
if (results.hasNext()) {
115118
BindingSet bindingSet = results.next();
119+
if (bindingSet.hasBinding("failure")) {
120+
if (bindingSet.getValue("failure").equals(BooleanLiteral.TRUE)) {
121+
throw new ShaclSparqlConstraintFailureException(shape, query, bindingSet,
122+
nextTarget.getActiveTarget(), dataGraph);
123+
}
124+
}
125+
116126
Value value = bindingSet.getValue("value");
117127
Value path = bindingSet.getValue("path");
118128

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2023 Eclipse RDF4J contributors.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
******************************************************************************/
11+
12+
package org.eclipse.rdf4j.sail.shacl;
13+
14+
import static org.junit.Assert.assertThrows;
15+
16+
import java.io.IOException;
17+
import java.io.StringReader;
18+
19+
import org.eclipse.rdf4j.query.Update;
20+
import org.eclipse.rdf4j.repository.RepositoryException;
21+
import org.eclipse.rdf4j.repository.sail.SailRepository;
22+
import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
23+
import org.eclipse.rdf4j.rio.RDFFormat;
24+
import org.eclipse.rdf4j.sail.memory.MemoryStore;
25+
import org.junit.jupiter.api.Assertions;
26+
import org.junit.jupiter.api.Test;
27+
28+
public class SparqlConstraintTest {
29+
30+
@Test
31+
public void testFailureBinding() throws IOException {
32+
33+
SailRepository sailRepository = new SailRepository(new ShaclSail(new MemoryStore()));
34+
35+
try (SailRepositoryConnection connection = sailRepository.getConnection()) {
36+
connection.add(new StringReader("" +
37+
"@prefix : <http://example.com/data/> .\n" +
38+
"@prefix ont: <http://example.com/ontology#> .\n" +
39+
"@prefix vocsh: <http://example.org/shape/> .\n" +
40+
"@prefix so: <http://www.ontotext.com/semantic-object/> .\n" +
41+
"@prefix affected: <http://www.ontotext.com/semantic-object/affected> .\n" +
42+
"@prefix res: <http://www.ontotext.com/semantic-object/result/> .\n" +
43+
"@prefix dct: <http://purl.org/dc/terms/> .\n" +
44+
"@prefix gn: <http://www.geonames.org/ontology#> .\n" +
45+
"@prefix owl: <http://www.w3.org/2002/07/owl#> .\n" +
46+
"@prefix puml: <http://plantuml.com/ontology#> .\n" +
47+
"@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n" +
48+
"@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n" +
49+
"@prefix skos: <http://www.w3.org/2004/02/skos/core#> .\n" +
50+
"@prefix void: <http://rdfs.org/ns/void#> .\n" +
51+
"@prefix wgs84: <http://www.w3.org/2003/01/geo/wgs84_pos#> .\n" +
52+
"@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n" +
53+
"@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
54+
"@prefix dash: <http://datashapes.org/dash#> .\n" +
55+
"@prefix rsx: <http://rdf4j.org/shacl-extensions#> .\n" +
56+
"@prefix ec: <http://www.ontotext.com/connectors/entity-change#> .\n" +
57+
"@prefix ecinst: <http://www.ontotext.com/connectors/entity-change/instance#> .\n" +
58+
"@prefix rdf4j: <http://rdf4j.org/schema/rdf4j#> .\n" +
59+
"@prefix ex: <http://example.com/ns#> .\n" +
60+
"\n" +
61+
"rdf4j:SHACLShapeGraph {\n" +
62+
"\n" +
63+
"ex:\n" +
64+
"\tsh:declare [\n" +
65+
"\t\tsh:prefix \"ex\" ;\n" +
66+
"\t\tsh:namespace \"http://example.com/ns#\"^^xsd:anyURI ;\n" +
67+
"\t] ;\n" +
68+
"\tsh:declare [\n" +
69+
"\t\tsh:prefix \"schema\" ;\n" +
70+
"\t\tsh:namespace \"http://schema.org/\"^^xsd:anyURI ;\n" +
71+
"\t] .\n" +
72+
"\n" +
73+
" ex:LanguageExampleShape\n" +
74+
" \ta sh:NodeShape ;\n" +
75+
" \tsh:targetClass ex:Country ;\n" +
76+
" \tsh:sparql [\n" +
77+
" \t\ta sh:SPARQLConstraint ; # This triple is optional\n" +
78+
" \t\tsh:message \"Values are literals with German language tag.\" ;\n" +
79+
" \t\tsh:prefixes ex: ;\n" +
80+
" \t\tsh:deactivated false ;\n" +
81+
" \t\tsh:select \"\"\"\n" +
82+
" \t\t\tSELECT $this (ex:germanLabel AS ?path) ?value ?failure\n" +
83+
" \t\t\tWHERE {\n" +
84+
" \t\t\t\t$this ex:germanLabel ?value .\n" +
85+
" \t\t\t\tBIND(isIri(?value) as ?failure)\n" +
86+
" \t\t\t}\n" +
87+
" \t\t\t\"\"\" ;\n" +
88+
" \t] .\n" +
89+
"}\n"), RDFFormat.TRIG);
90+
91+
Update update = connection.prepareUpdate("PREFIX ex: <http://example.com/ns#>\n" +
92+
"PREFIX owl: <http://www.w3.org/2002/07/owl#>\n" +
93+
"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n" +
94+
"PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n" +
95+
"PREFIX sh: <http://www.w3.org/ns/shacl#>\n" +
96+
"PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>\n" +
97+
"\n" +
98+
"INSERT DATA {\n" +
99+
"ex:InvalidCountry a ex:Country .\n" +
100+
"ex:InvalidCountry ex:germanLabel ex:invalidValue .\n" +
101+
"}\n");
102+
103+
// assert exception is thrown
104+
RepositoryException repositoryException = assertThrows(RepositoryException.class, update::execute);
105+
Throwable cause = repositoryException.getCause().getCause();
106+
Assertions.assertEquals(
107+
"org.eclipse.rdf4j.sail.shacl.ast.ShaclSparqlConstraintFailureException: The ?failure variable was true for <http://example.com/ns#InvalidCountry> in shape <http://example.com/ns#LanguageExampleShape> with result resultBindingSet: [this=http://example.com/ns#InvalidCountry;value=http://example.com/ns#invalidValue;failure=\"true\"^^<http://www.w3.org/2001/XMLSchema#boolean>;path=http://example.com/ns#germanLabel] and dataGraph: [] and query:PREFIX schema: <http://schema.org/> \n"
108+
+
109+
"PREFIX ex: <http://example.com/ns#> \n" +
110+
"\n" +
111+
"\n" +
112+
"\n" +
113+
" \t\t\tSELECT $this (ex:germanLabel AS ?path) ?value ?failure\n" +
114+
" \t\t\tWHERE {\n" +
115+
" \t\t\t\t$this ex:germanLabel ?value .\n" +
116+
" \t\t\t\tBIND(isIri(?value) as ?failure)\n" +
117+
" \t\t\t}\n" +
118+
" \t\t\t",
119+
cause.toString());
120+
121+
}
122+
123+
}
124+
125+
}

0 commit comments

Comments
 (0)