SparqlTarget.java
/*******************************************************************************
* 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.targets;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Namespace;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.SHACL;
import org.eclipse.rdf4j.sail.shacl.ast.ShaclPrefixParser;
import org.eclipse.rdf4j.sail.shacl.ast.SparqlFragment;
import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher;
import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.ConstraintComponent;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.AllTargetsPlanNode;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ExternalFilterByQuery;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNode;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.Select;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.UnBufferedPlanNode;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ValidationTuple;
import org.eclipse.rdf4j.sail.shacl.wrapper.data.ConnectionsGroup;
import org.eclipse.rdf4j.sail.shacl.wrapper.data.RdfsSubClassOfReasoner;
import org.eclipse.rdf4j.sail.shacl.wrapper.shape.ShapeSource;
public class SparqlTarget extends Target {
private final Resource id;
private Literal originalSelect;
private String select;
private final Set<Namespace> namespaces;
private final Model prefixes;
public SparqlTarget(Resource id, ShapeSource shapeSource) {
this.id = id;
try (Stream<Value> objects = shapeSource.getObjects(id, ShapeSource.Predicates.SELECT)) {
objects.forEach(literal -> {
if (select != null) {
throw new IllegalStateException("Multiple sh:select queries found for constraint component " + id);
}
if (!(literal.isLiteral())) {
throw new IllegalStateException("sh:select must be a literal for constraint component " + id);
}
select = literal.stringValue();
originalSelect = ((Literal) literal);
});
}
if (select == null) {
throw new IllegalStateException("No sh:select query found for constraint component " + id);
}
var shaclNamespaces = ShaclPrefixParser.extractNamespaces(id, shapeSource);
prefixes = shaclNamespaces.getModel();
namespaces = shaclNamespaces.getNamespaces();
}
@Override
public void toModel(Resource subject, IRI predicate, Model model, Set<Resource> cycleDetection) {
model.add(subject, SHACL.TARGET_PROP, id);
model.add(id, SHACL.SELECT, originalSelect);
model.add(id, RDF.TYPE, SHACL.SPARQL_TARGET);
model.addAll(prefixes);
}
@Override
public SparqlFragment getTargetQueryFragment(StatementMatcher.Variable subject, StatementMatcher.Variable object,
RdfsSubClassOfReasoner rdfsSubClassOfReasoner,
StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider, Set<String> inheritedVarNames) {
String query = "{ select (?this AS " + object.asSparqlVariable() + ") where {\n" +
"{\n" + select + "\n}\n" +
"} }";
return SparqlFragment.bgp(namespaces, query, false);
}
@Override
public IRI getPredicate() {
return SHACL.SPARQL_TARGET;
}
@Override
public PlanNode getAdded(ConnectionsGroup connectionsGroup, Resource[] dataGraph, ConstraintComponent.Scope scope) {
SparqlFragment sparqlFragment = SparqlFragment.bgp(namespaces, select, false);
List<String> varNames = List.of("this");
return new Select(connectionsGroup.getBaseConnection(), sparqlFragment, null,
new AllTargetsPlanNode.AllTargetsBindingSetMapper(varNames, scope, false, dataGraph), dataGraph);
}
@Override
public PlanNode getTargetFilter(ConnectionsGroup connectionsGroup, Resource[] dataGraph, PlanNode parent) {
SparqlFragment sparqlFragment = SparqlFragment.bgp(namespaces, select, false);
// TODO: this is a slow way to solve this problem! We should use bulk operations.
return new ExternalFilterByQuery(connectionsGroup.getBaseConnection(), dataGraph, parent, sparqlFragment,
StatementMatcher.Variable.THIS,
ValidationTuple::getActiveTarget, null, connectionsGroup).getTrueNode(UnBufferedPlanNode.class);
}
@Override
public Set<Namespace> getNamespaces() {
return namespaces;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SparqlTarget that = (SparqlTarget) o;
return select.equals(that.select);
}
@Override
public int hashCode() {
return select.hashCode() + "SparqlTarget".hashCode();
}
}