Skip to content

Commit 1dc2438

Browse files
committed
GH-1112 support for SPARQL based validation
1 parent 09afe01 commit 1dc2438

2 files changed

Lines changed: 164 additions & 8 deletions

File tree

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

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,27 @@
1616
import java.util.Collections;
1717
import java.util.HashSet;
1818
import java.util.List;
19+
import java.util.Optional;
1920
import java.util.Set;
21+
import java.util.function.Function;
2022

2123
import org.eclipse.rdf4j.model.Namespace;
2224
import org.eclipse.rdf4j.model.Resource;
2325
import org.eclipse.rdf4j.model.Value;
26+
import org.eclipse.rdf4j.query.BindingSet;
2427
import org.eclipse.rdf4j.sail.SailConnection;
2528
import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable;
2629
import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.ConstraintComponent;
2730
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.EmptyNode;
2831
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNode;
2932
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.Select;
30-
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ValidationReportNode;
3133
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ValidationTuple;
3234
import org.eclipse.rdf4j.sail.shacl.results.ValidationResult;
3335

3436
public class ValidationQuery {
3537

3638
private final Set<Namespace> namespaces = new HashSet<>();
39+
private ValidationResultGenerator validationResultGenerator;
3740
private String query;
3841
private ConstraintComponent.Scope scope;
3942
private ConstraintComponent.Scope scope_validationReport;
@@ -54,6 +57,7 @@ public class ValidationQuery {
5457

5558
private Severity severity;
5659
private Shape shape;
60+
private List<Variable<?>> extraVariables = List.of();
5761

5862
public ValidationQuery(Collection<Namespace> namespaces, String query, List<Variable<Value>> targets,
5963
Variable<Value> value,
@@ -90,6 +94,8 @@ public ValidationQuery(Collection<Namespace> namespaces, String query, List<Vari
9094
this.constraintComponent = constraintComponent;
9195
this.severity = severity;
9296
this.shape = shape;
97+
this.validationResultGenerator = new ValidationResultGenerator();
98+
9399
}
94100

95101
public ValidationQuery(Set<Namespace> namespaces, String query, ConstraintComponent.Scope scope,
@@ -101,6 +107,7 @@ public ValidationQuery(Set<Namespace> namespaces, String query, ConstraintCompon
101107
this.variables = Collections.unmodifiableList(variables);
102108
this.targetIndex = targetIndex;
103109
this.valueIndex = valueIndex;
110+
this.validationResultGenerator = new ValidationResultGenerator();
104111
}
105112

106113
/**
@@ -169,41 +176,78 @@ public PlanNode getValidationPlan(SailConnection baseConnection, Resource[] data
169176

170177
Select select = new Select(baseConnection, fullQueryString, bindings -> {
171178

179+
var validationResultFunction = validationResultGenerator.getValidationTupleValidationResultFunction(this,
180+
shapesGraphs, bindings);
181+
182+
ValidationTuple validationTuple;
183+
172184
if (scope_validationReport == ConstraintComponent.Scope.propertyShape) {
173185
if (propertyShapeWithValue_validationReport) {
174-
return new ValidationTuple(bindings.getValue(getTargetVariable(true)),
186+
validationTuple = new ValidationTuple(bindings.getValue(getTargetVariable(true)),
175187
bindings.getValue(getValueVariable(true)),
176188
scope_validationReport, true, dataGraph);
177189
} else {
178-
return new ValidationTuple(bindings.getValue(getTargetVariable(true)),
190+
validationTuple = new ValidationTuple(bindings.getValue(getTargetVariable(true)),
179191
scope_validationReport, false, dataGraph);
180192
}
181193

182194
} else {
183-
return new ValidationTuple(bindings.getValue(getTargetVariable(true)),
195+
validationTuple = new ValidationTuple(bindings.getValue(getTargetVariable(true)),
184196
scope_validationReport, true, dataGraph);
185197
}
186198

199+
return validationTuple.addValidationResult(validationResultFunction);
200+
187201
}, dataGraph);
188202

189-
return new ValidationReportNode(select, t -> {
190-
return new ValidationResult(t.getActiveTarget(), t.getValue(), shape,
191-
constraintComponent_validationReport, severity, t.getScope(), t.getContexts(), shapesGraphs);
192-
});
203+
return select;
204+
205+
}
206+
207+
public static class ValidationResultGenerator {
208+
209+
public Function<ValidationTuple, ValidationResult> getValidationTupleValidationResultFunction(
210+
ValidationQuery validationQuery, Resource[] shapesGraphs, BindingSet bindings) {
211+
return t -> new ValidationResult(t.getActiveTarget(), t.getValue(), validationQuery.shape,
212+
validationQuery.constraintComponent_validationReport, validationQuery.severity, t.getScope(),
213+
t.getContexts(), shapesGraphs);
214+
}
215+
216+
}
193217

218+
public void setValidationResultGenerator(List<Variable<?>> extraVariables,
219+
ValidationResultGenerator validationResultGenerator) {
220+
this.validationResultGenerator = validationResultGenerator;
221+
this.extraVariables = extraVariables;
194222
}
195223

196224
private String getFullQueryString() {
225+
String extraVariablesString;
226+
if (!extraVariables.isEmpty()) {
227+
Optional<String> reduce = extraVariables.stream()
228+
.map(Variable::asSparqlVariable)
229+
.reduce((a, b) -> a + " " + b);
230+
if (reduce.isPresent()) {
231+
extraVariablesString = reduce.get() + " ";
232+
} else {
233+
extraVariablesString = "";
234+
}
235+
} else
236+
extraVariablesString = "";
237+
197238
if (scope_validationReport == ConstraintComponent.Scope.propertyShape
198239
&& propertyShapeWithValue_validationReport) {
240+
199241
return ShaclPrefixParser.toSparqlPrefixes(namespaces) + "\nSELECT DISTINCT " +
200242
"?" + getTargetVariable(true) + " " +
201243
"?" + getValueVariable(true) + " " +
244+
extraVariablesString +
202245
"WHERE {\n" + query + "\n}";
203246

204247
} else {
205248
return ShaclPrefixParser.toSparqlPrefixes(namespaces) + "\nSELECT DISTINCT " +
206249
"?" + getTargetVariable(true) + " " +
250+
extraVariablesString +
207251
"WHERE {\n" + query + "\n}";
208252
}
209253
}
@@ -267,6 +311,18 @@ public void makeCurrentStateValidationReport() {
267311
propertyShapeWithValue_validationReport = propertyShapeWithValue;
268312
}
269313

314+
public Shape getShape() {
315+
return shape;
316+
}
317+
318+
public Severity getSeverity() {
319+
return severity;
320+
}
321+
322+
public ConstraintComponent getConstraintComponent_validationReport() {
323+
return constraintComponent_validationReport;
324+
}
325+
270326
// used for sh:deactivated
271327
public static class Deactivated extends ValidationQuery {
272328

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

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.List;
1919
import java.util.Objects;
2020
import java.util.Set;
21+
import java.util.function.Function;
2122
import java.util.stream.Collectors;
2223

2324
import org.eclipse.rdf4j.model.IRI;
@@ -34,6 +35,8 @@
3435
import org.eclipse.rdf4j.sail.shacl.ast.Shape;
3536
import org.eclipse.rdf4j.sail.shacl.ast.SparqlFragment;
3637
import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher;
38+
import org.eclipse.rdf4j.sail.shacl.ast.ValidationApproach;
39+
import org.eclipse.rdf4j.sail.shacl.ast.ValidationQuery;
3740
import org.eclipse.rdf4j.sail.shacl.ast.paths.Path;
3841
import org.eclipse.rdf4j.sail.shacl.ast.paths.SimplePath;
3942
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.BufferedSplitter;
@@ -434,6 +437,103 @@ public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, Resource[]
434437
throw new UnsupportedOperationException();
435438
}
436439

440+
@Override
441+
public ValidationQuery generateSparqlValidationQuery(ConnectionsGroup connectionsGroup,
442+
ValidationSettings validationSettings, boolean negatePlan, boolean negateChildren, Scope scope) {
443+
StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider = new StatementMatcher.StableRandomVariableProvider();
444+
445+
EffectiveTarget effectiveTarget = getTargetChain().getEffectiveTarget(scope,
446+
connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider);
447+
String query = effectiveTarget.getQuery(false);
448+
449+
StatementMatcher.Variable<Value> predicateVariable = stableRandomVariableProvider.next();
450+
StatementMatcher.Variable<Value> objectVariable = stableRandomVariableProvider.next();
451+
452+
StatementMatcher.Variable<Value> value;
453+
454+
if (scope == Scope.nodeShape) {
455+
456+
value = null;
457+
458+
var target = effectiveTarget.getTargetVar();
459+
460+
query += "\n" + getFilter(target, predicateVariable, objectVariable);
461+
462+
} else {
463+
value = new StatementMatcher.Variable<>("value");
464+
465+
SparqlFragment sparqlFragment = getTargetChain().getPath()
466+
.map(p -> p.getTargetQueryFragment(effectiveTarget.getTargetVar(), value,
467+
connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider, Set.of()))
468+
.orElseThrow(IllegalStateException::new);
469+
470+
String pathQuery = sparqlFragment.getFragment();
471+
472+
query += "\n" + pathQuery;
473+
query += "\n" + getFilter(value, predicateVariable, objectVariable);
474+
}
475+
476+
var allTargetVariables = effectiveTarget.getAllTargetVariables();
477+
478+
ValidationQuery validationQuery = new ValidationQuery(getTargetChain().getNamespaces(), query,
479+
allTargetVariables, value, scope, this,
480+
null, null);
481+
482+
if (produceValidationReports) {
483+
validationQuery = validationQuery
484+
.withShape(shape)
485+
.withSeverity(shape.getSeverity());
486+
487+
validationQuery.makeCurrentStateValidationReport();
488+
489+
validationQuery.setValidationResultGenerator(List.of(predicateVariable, objectVariable),
490+
new ValidationQuery.ValidationResultGenerator() {
491+
@Override
492+
public Function<ValidationTuple, ValidationResult> getValidationTupleValidationResultFunction(
493+
ValidationQuery validationQuery, Resource[] shapesGraphs, BindingSet bindings) {
494+
Function<ValidationTuple, ValidationResult> validationResultFunction = t -> {
495+
ValidationResult validationResult = new ValidationResult(t.getActiveTarget(),
496+
bindings.getValue(objectVariable.getName()), validationQuery.getShape(),
497+
validationQuery.getConstraintComponent_validationReport(),
498+
validationQuery.getSeverity(), t.getScope(), t.getContexts(), shapesGraphs);
499+
validationResult.setPathIri(bindings.getValue(predicateVariable.getName()));
500+
return validationResult;
501+
};
502+
return validationResultFunction;
503+
}
504+
});
505+
506+
}
507+
508+
return validationQuery;
509+
}
510+
511+
private String getFilter(StatementMatcher.Variable<Value> target,
512+
StatementMatcher.Variable<Value> predicateVariable, StatementMatcher.Variable<Value> objectVariable) {
513+
514+
SparqlFragment bgp = SparqlFragment.bgp(List.of(),
515+
target.asSparqlVariable() + " " + predicateVariable.asSparqlVariable() + " "
516+
+ objectVariable.asSparqlVariable() + ".",
517+
List.of());
518+
String notInSparqlFilter = "FILTER( " + predicateVariable.asSparqlVariable() + " NOT IN( "
519+
+ allAllowedPredicates.stream().map(p -> "<" + p.toString() + ">").collect(Collectors.joining(", "))
520+
+ " ) )";
521+
SparqlFragment sparqlFragmentFilter = SparqlFragment.bgp(List.of(), notInSparqlFilter, List.of());
522+
SparqlFragment sparqlFragment = SparqlFragment.join(List.of(bgp, sparqlFragmentFilter));
523+
524+
return sparqlFragment.getFragment();
525+
}
526+
527+
@Override
528+
public ValidationApproach getPreferredValidationApproach(ConnectionsGroup connectionsGroup) {
529+
return super.getPreferredValidationApproach(connectionsGroup);
530+
}
531+
532+
@Override
533+
public ValidationApproach getOptimalBulkValidationApproach() {
534+
return ValidationApproach.SPARQL;
535+
}
536+
437537
@Override
438538
public boolean requiresEvaluation(ConnectionsGroup connectionsGroup, Scope scope, Resource[] dataGraph,
439539
StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider) {

0 commit comments

Comments
 (0)