Skip to content

Commit

Permalink
GH-4686 support ?failure var in SparqlConstraint select queries (#4807)
Browse files Browse the repository at this point in the history
  • Loading branch information
hmottestad authored Oct 12, 2023
2 parents f177ad4 + b35e6f2 commit 86cbf5b
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*******************************************************************************
* Copyright (c) 2023 Eclipse RDF4J contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
******************************************************************************/

package org.eclipse.rdf4j.sail.shacl.ast;

import java.util.Arrays;

import org.eclipse.rdf4j.common.exception.RDF4JException;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.query.BindingSet;

/**
* An exception thrown when the ?failure var is true for a SPARQL constraint select query.
*/
public class ShaclSparqlConstraintFailureException extends RDF4JException {

private final Shape shape;
private final String query;
private final BindingSet resultBindingSet;
private final Value focusNode;
private final Resource[] dataGraph;

public ShaclSparqlConstraintFailureException(Shape shape, String query, BindingSet resultBindingSet,
Value focusNode, Resource[] dataGraph) {
super("The ?failure variable was true for " + valueToString(focusNode) + " in shape "
+ resourceToString(shape.getId()) + " with result resultBindingSet: " + resultBindingSet.toString()
+ " and dataGraph: " + Arrays.toString(dataGraph) + " and query:" + query);
this.shape = shape;
this.query = query;
this.resultBindingSet = resultBindingSet;
this.focusNode = focusNode;
this.dataGraph = dataGraph;
}

public String getShape() {
return shape.toString();
}

public String getQuery() {
return query;
}

public BindingSet getResultBindingSet() {
return resultBindingSet;
}

public Value getFocusNode() {
return focusNode;
}

public Resource[] getDataGraph() {
return dataGraph;
}

private static String resourceToString(Resource id) {
assert id != null;
if (id == null) {
return "null";
}
if (id.isIRI()) {
return "<" + id.stringValue() + ">";
}
if (id.isBNode()) {
return id.toString();
}
if (id.isTriple()) {
return "TRIPLE " + id;
}
return id.toString();
}

private static String valueToString(Value value) {
assert value != null;
if (value == null) {
return "null";
}
if (value.isResource()) {
return resourceToString((Resource) value);
}
if (value.isLiteral()) {
return value.toString();
}
return value.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ abstract public class Shape implements ConstraintComponent, Identifiable {
private static final Logger logger = LoggerFactory.getLogger(Shape.class);
protected boolean produceValidationReports;

Resource id;
private Resource id;
TargetChain targetChain;

List<Target> target = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.impl.BooleanLiteral;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.Dataset;
import org.eclipse.rdf4j.query.MalformedQueryException;
Expand All @@ -28,8 +29,10 @@
import org.eclipse.rdf4j.sail.SailConnection;
import org.eclipse.rdf4j.sail.SailException;
import org.eclipse.rdf4j.sail.memory.MemoryStoreConnection;
import org.eclipse.rdf4j.sail.shacl.ast.ShaclSparqlConstraintFailureException;
import org.eclipse.rdf4j.sail.shacl.ast.Shape;
import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.ConstraintComponent;
import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.SparqlConstraintComponent;
import org.eclipse.rdf4j.sail.shacl.results.ValidationResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -47,7 +50,7 @@ public class SparqlConstraintSelect implements PlanNode {
private final String query;
private final Resource[] dataGraph;
private final boolean produceValidationReports;
private final ConstraintComponent constraintComponent;
private final SparqlConstraintComponent constraintComponent;
private final Shape shape;
private final String[] variables;
private final ConstraintComponent.Scope scope;
Expand All @@ -58,7 +61,7 @@ public class SparqlConstraintSelect implements PlanNode {

public SparqlConstraintSelect(SailConnection connection, PlanNode targets, String query,
ConstraintComponent.Scope scope,
Resource[] dataGraph, boolean produceValidationReports, ConstraintComponent constraintComponent,
Resource[] dataGraph, boolean produceValidationReports, SparqlConstraintComponent constraintComponent,
Shape shape) {
this.connection = connection;
this.targets = targets;
Expand Down Expand Up @@ -113,6 +116,13 @@ private void calculateNext() {

if (results.hasNext()) {
BindingSet bindingSet = results.next();
if (bindingSet.hasBinding("failure")) {
if (bindingSet.getValue("failure").equals(BooleanLiteral.TRUE)) {
throw new ShaclSparqlConstraintFailureException(shape, query, bindingSet,
nextTarget.getActiveTarget(), dataGraph);
}
}

Value value = bindingSet.getValue("value");
Value path = bindingSet.getValue("path");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*******************************************************************************
* Copyright (c) 2023 Eclipse RDF4J contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
******************************************************************************/

package org.eclipse.rdf4j.sail.shacl;

import static org.junit.Assert.assertThrows;

import java.io.IOException;
import java.io.StringReader;

import org.eclipse.rdf4j.query.Update;
import org.eclipse.rdf4j.repository.RepositoryException;
import org.eclipse.rdf4j.repository.sail.SailRepository;
import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.sail.memory.MemoryStore;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class SparqlConstraintTest {

@Test
public void testFailureBinding() throws IOException {

SailRepository sailRepository = new SailRepository(new ShaclSail(new MemoryStore()));

try (SailRepositoryConnection connection = sailRepository.getConnection()) {
connection.add(new StringReader("" +
"@prefix : <http://example.com/data/> .\n" +
"@prefix ont: <http://example.com/ontology#> .\n" +
"@prefix vocsh: <http://example.org/shape/> .\n" +
"@prefix so: <http://www.ontotext.com/semantic-object/> .\n" +
"@prefix affected: <http://www.ontotext.com/semantic-object/affected> .\n" +
"@prefix res: <http://www.ontotext.com/semantic-object/result/> .\n" +
"@prefix dct: <http://purl.org/dc/terms/> .\n" +
"@prefix gn: <http://www.geonames.org/ontology#> .\n" +
"@prefix owl: <http://www.w3.org/2002/07/owl#> .\n" +
"@prefix puml: <http://plantuml.com/ontology#> .\n" +
"@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n" +
"@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n" +
"@prefix skos: <http://www.w3.org/2004/02/skos/core#> .\n" +
"@prefix void: <http://rdfs.org/ns/void#> .\n" +
"@prefix wgs84: <http://www.w3.org/2003/01/geo/wgs84_pos#> .\n" +
"@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n" +
"@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
"@prefix dash: <http://datashapes.org/dash#> .\n" +
"@prefix rsx: <http://rdf4j.org/shacl-extensions#> .\n" +
"@prefix ec: <http://www.ontotext.com/connectors/entity-change#> .\n" +
"@prefix ecinst: <http://www.ontotext.com/connectors/entity-change/instance#> .\n" +
"@prefix rdf4j: <http://rdf4j.org/schema/rdf4j#> .\n" +
"@prefix ex: <http://example.com/ns#> .\n" +
"\n" +
"rdf4j:SHACLShapeGraph {\n" +
"\n" +
"ex:\n" +
"\tsh:declare [\n" +
"\t\tsh:prefix \"ex\" ;\n" +
"\t\tsh:namespace \"http://example.com/ns#\"^^xsd:anyURI ;\n" +
"\t] ;\n" +
"\tsh:declare [\n" +
"\t\tsh:prefix \"schema\" ;\n" +
"\t\tsh:namespace \"http://schema.org/\"^^xsd:anyURI ;\n" +
"\t] .\n" +
"\n" +
" ex:LanguageExampleShape\n" +
" \ta sh:NodeShape ;\n" +
" \tsh:targetClass ex:Country ;\n" +
" \tsh:sparql [\n" +
" \t\ta sh:SPARQLConstraint ; # This triple is optional\n" +
" \t\tsh:message \"Values are literals with German language tag.\" ;\n" +
" \t\tsh:prefixes ex: ;\n" +
" \t\tsh:deactivated false ;\n" +
" \t\tsh:select \"\"\"\n" +
" \t\t\tSELECT $this (ex:germanLabel AS ?path) ?value ?failure\n" +
" \t\t\tWHERE {\n" +
" \t\t\t\t$this ex:germanLabel ?value .\n" +
" \t\t\t\tBIND(isIri(?value) as ?failure)\n" +
" \t\t\t}\n" +
" \t\t\t\"\"\" ;\n" +
" \t] .\n" +
"}\n"), RDFFormat.TRIG);

Update update = connection.prepareUpdate("PREFIX ex: <http://example.com/ns#>\n" +
"PREFIX owl: <http://www.w3.org/2002/07/owl#>\n" +
"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n" +
"PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n" +
"PREFIX sh: <http://www.w3.org/ns/shacl#>\n" +
"PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>\n" +
"\n" +
"INSERT DATA {\n" +
"ex:InvalidCountry a ex:Country .\n" +
"ex:InvalidCountry ex:germanLabel ex:invalidValue .\n" +
"}\n");

// assert exception is thrown
RepositoryException repositoryException = assertThrows(RepositoryException.class, update::execute);
Throwable cause = repositoryException.getCause().getCause();
Assertions.assertEquals(
"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"
+
"PREFIX ex: <http://example.com/ns#> \n" +
"\n" +
"\n" +
"\n" +
" \t\t\tSELECT $this (ex:germanLabel AS ?path) ?value ?failure\n" +
" \t\t\tWHERE {\n" +
" \t\t\t\t$this ex:germanLabel ?value .\n" +
" \t\t\t\tBIND(isIri(?value) as ?failure)\n" +
" \t\t\t}\n" +
" \t\t\t",
cause.toString());

}

}

}

0 comments on commit 86cbf5b

Please sign in to comment.